eu.davidea.flexibleadapter.FlexibleAdapter.java Source code

Java tutorial

Introduction

Here is the source code for eu.davidea.flexibleadapter.FlexibleAdapter.java

Source

/*
 * Copyright 2015-2016 Davide Steduto
 *
 * 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 eu.davidea.flexibleadapter;

import android.annotation.SuppressLint;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.CallSuper;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import eu.davidea.flexibleadapter.common.SmoothScrollGridLayoutManager;
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager;
import eu.davidea.flexibleadapter.helpers.ItemTouchHelperCallback;
import eu.davidea.flexibleadapter.helpers.StickyHeaderHelper;
import eu.davidea.flexibleadapter.items.IExpandable;
import eu.davidea.flexibleadapter.items.IFilterable;
import eu.davidea.flexibleadapter.items.IFlexible;
import eu.davidea.flexibleadapter.items.IHeader;
import eu.davidea.flexibleadapter.items.ISectionable;
import eu.davidea.flexibleadapter.utils.Utils;
import eu.davidea.viewholders.ExpandableViewHolder;
import eu.davidea.viewholders.FlexibleViewHolder;

/**
 * This class is backed by an ArrayList of arbitrary objects of <b>T</b>, where <b>T</b> is
 * your Model object containing the data, with version 5.0.0 it must implement {@link IFlexible}
 * interface. Read <a href="https://github.com/davideas/FlexibleAdapter/wiki">Wiki page</a> on
 * Github for more details.
 * <p>This class provides a set of standard methods to handle changes on the data set such as
 * filtering, adding, removing, moving and animating an item.</p>
 * With version 5.0.0, this Adapter supports a set of standard methods for Headers/Sections to
 * expand and collapse an Expandable item, to Drag&Drop and Swipe any item.
 * <p><b>NOTE:</b> This Adapter supports multi level of Expandable, but do not enable Drag&Drop.
 * Something might not work as expected, so better to change approach in favor of a clearer
 * design/layout: Open the sub list in a new Activity/Fragment...
 * <br/>Instead, this extra level of expansion is useful in situations where information is in
 * read only mode or with action buttons.</p>
 *
 * @author Davide Steduto
 * @see AnimatorAdapter
 * @see SelectableAdapter
 * @see IFlexible
 * @see FlexibleViewHolder
 * @see ExpandableViewHolder
 * @since 03/05/2015 Created
 * <br/>16/01/2016 Expandable items
 * <br/>24/01/2016 Drag&Drop, Swipe
 * <br/>30/01/2016 Class now extends {@link AnimatorAdapter} that extends {@link SelectableAdapter}
 * <br/>02/02/2016 New code reorganization, new item interfaces and full refactoring
 * <br/>08/02/2016 Headers/Sections
 * <br/>10/02/2016 The class is not abstract anymore, it is ready to be used
 * <br/>20/02/2016 Sticky headers
 * <br/>22/04/2016 Endless Scrolling
 * <br/>09/07/2016 FilterAsyncTask (performance on big list)
 */
@SuppressWarnings({ "Range", "unused", "unchecked", "ConstantConditions", "SuspiciousMethodCalls" })
public class FlexibleAdapter<T extends IFlexible> extends AnimatorAdapter
        implements ItemTouchHelperCallback.AdapterCallback {

    private static final String TAG = FlexibleAdapter.class.getSimpleName();
    private static final String EXTRA_PARENT = TAG + "_parentSelected";
    private static final String EXTRA_CHILD = TAG + "_childSelected";
    private static final String EXTRA_HEADERS = TAG + "_headersShown";
    private static final String EXTRA_LEVEL = TAG + "_selectedLevel";
    private static final String EXTRA_SEARCH = TAG + "_searchText";
    /**
     * Handler operations
     */
    private static final int UPDATE = 0, FILTER = 1, CONFIRM_DELETE = 2, LOAD_MORE_COMPLETE = 8,
            LOAD_MORE_RESET = 9;

    /**
     * The main container for ALL items.
     */
    private List<T> mItems;

    /**
     * HashSet and AsyncTask objects, will increase performance in big list
     */
    private Set<T> hashItems;
    private List<Notification> notifications;
    private FilterAsyncTask mFilterAsyncTask;

    /**
     * Handler for delayed actions.
     * <p>You can use and override this Handler, but you must keep the "What" by calling super():
     * <br/>0 = updateDataSet.
     * <br/>1 = filterItems, optionally delayed.
     * <br/>2 = deleteConfirmed when Undo timeout is over.
     * <br/>8 = remove the progress item from the list, optionally delayed.
     * <br/>9 = reset flag to load more items, delayed.</p>
     * <b>Note:</b> numbers 0-9 are reserved for the Adapter, use others.
     */
    protected Handler mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
        public boolean handleMessage(Message message) {
            switch (message.what) {
            case UPDATE: //updateDataSet OR
            case FILTER: //filterItems
                if (mFilterAsyncTask != null)
                    mFilterAsyncTask.cancel(true);
                mFilterAsyncTask = new FilterAsyncTask(message.what, (List<T>) message.obj);
                mFilterAsyncTask.execute();
                return true;
            case CONFIRM_DELETE: //confirm delete
                OnDeleteCompleteListener listener = (OnDeleteCompleteListener) message.obj;
                if (listener != null)
                    listener.onDeleteConfirmed();
                emptyBin();
                return true;
            case LOAD_MORE_COMPLETE: //onLoadMore remove progress item
                deleteProgressItem();
                return true;
            case LOAD_MORE_RESET: //onLoadMore reset
                resetOnLoadMore();
                return true;
            }
            return false;
        }
    });

    /* Used to save deleted items and to recover them (Undo) */
    public static final long UNDO_TIMEOUT = 5000L;
    private List<RestoreInfo> mRestoreList;
    private boolean restoreSelection = false, multiRange = false, unlinkOnRemoveHeader = false,
            removeOrphanHeaders = false, permanentDelete = true, adjustSelected = true;

    /* Header/Section items */
    private List<IHeader> mOrphanHeaders;
    private boolean headersShown = false, headersSticky = false, recursive = false;
    private StickyHeaderHelper mStickyHeaderHelper;

    /* ViewTypes */
    protected LayoutInflater mInflater;
    @SuppressLint("UseSparseArrays") //We can usually count Type instances on the fingers of a hand
    private HashMap<Integer, T> mTypeInstances = new HashMap<>();
    private boolean autoMap = false;

    /* Filter */
    private String mSearchText = "", mOldSearchText = "";
    private Set<IExpandable> mExpandedFilterFlags;
    private boolean notifyChangeOfUnfilteredItems = false, filtering = false, notifyMoveOfFilteredItems = false;
    private static int mAnimateToLimit = 600;

    /* Expandable flags */
    private int minCollapsibleLevel = 0, selectedLevel = -1;
    private boolean scrollOnExpand = false, collapseOnExpand = false, childSelected = false, parentSelected = false;

    /* Drag&Drop and Swipe helpers */
    private boolean handleDragEnabled = false;
    private ItemTouchHelperCallback mItemTouchHelperCallback;
    private ItemTouchHelper mItemTouchHelper;

    /* EndlessScroll */
    private int mEndlessScrollThreshold = 1;
    private boolean mLoading = false;
    private T mProgressItem;

    /* Listeners */
    protected OnUpdateListener mUpdateListener;
    public OnItemClickListener mItemClickListener;
    public OnItemLongClickListener mItemLongClickListener;
    protected OnItemMoveListener mItemMoveListener;
    protected OnItemSwipeListener mItemSwipeListener;
    protected OnStickyHeaderChangeListener mStickyHeaderChangeListener;
    protected EndlessScrollListener mEndlessScrollListener;

    /*--------------*/
    /* CONSTRUCTORS */
    /*--------------*/

    /**
     * Simple Constructor with NO listeners!
     *
     * @param items items to display.
     * @since 4.2.0
     */
    public FlexibleAdapter(@Nullable List<T> items) {
        this(items, null);
    }

    /**
     * Main Constructor with all managed listeners for ViewHolder and the Adapter itself.
     * <p>The listener must be a single instance of a class, usually <i>Activity</i> or <i>Fragment</i>,
     * where you can implement how to handle the different events.</p>
     * Any write operation performed on the items list is <u>synchronized</u>.
     * <p><b>PASS ALWAYS A <u>COPY</u> OF THE ORIGINAL LIST</b>: <i>new ArrayList&lt;T&gt;(originalList);</i></p>
     *
     * @param items     items to display
     * @param listeners can be an instance of:
     *                  <ul>
     *                  <li>{@link OnUpdateListener}
     *                  <li>{@link OnItemClickListener}
     *                  <li>{@link OnItemLongClickListener}
     *                  <li>{@link OnItemMoveListener}
     *                  <li>{@link OnItemSwipeListener}
     *                  <li>{@link OnStickyHeaderChangeListener}
     *                  </ul>
     * @since 5.0.0-b1
     */
    public FlexibleAdapter(@Nullable List<T> items, @Nullable Object listeners) {
        this(items, listeners, false);
    }

    /**
     * Same as {@link #FlexibleAdapter(List, Object)} with possibility to set stableIds.
     * <p><b>Note:</b> Setting true allows the RecyclerView to rebind only items really changed
     * after a refresh or after swapping Adapter. This increase performance, you loose scrolling
     * animations.</p>
     * Set {@code true} if items implements {@code hashcode()} and have stable ids. The method
     * {@link #setHasStableIds(boolean)} will be called.
     *
     * @param stableIds set {@code true} if items implements {@code hashcode()} and have stable ids.
     * @since 5.0.0-b8
     */
    public FlexibleAdapter(@Nullable List<T> items, @Nullable Object listeners, boolean stableIds) {
        super(stableIds);
        if (items == null)
            mItems = new ArrayList<>();
        else
            mItems = items;
        mRestoreList = new ArrayList<>();
        mOrphanHeaders = new ArrayList<>();

        //Create listeners instances
        initializeListeners(listeners);

        //Get notified when items are inserted or removed (it adjusts selected positions)
        registerAdapterDataObserver(new AdapterDataObserver());
    }

    /**
     * Initializes the listener(s) of this Adapter.
     * <p>This method is automatically called from the Constructor.</p>
     *
     * @param listeners the object(s) instance(s) of any listener
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b6
     */
    public FlexibleAdapter initializeListeners(@Nullable Object listeners) {
        if (listeners instanceof OnUpdateListener) {
            mUpdateListener = (OnUpdateListener) listeners;
            mUpdateListener.onUpdateEmptyView(getItemCount());
        }
        if (listeners instanceof OnItemClickListener)
            mItemClickListener = (OnItemClickListener) listeners;
        if (listeners instanceof OnItemLongClickListener)
            mItemLongClickListener = (OnItemLongClickListener) listeners;
        if (listeners instanceof OnItemMoveListener)
            mItemMoveListener = (OnItemMoveListener) listeners;
        if (listeners instanceof OnItemSwipeListener)
            mItemSwipeListener = (OnItemSwipeListener) listeners;
        if (listeners instanceof OnStickyHeaderChangeListener)
            mStickyHeaderChangeListener = (OnStickyHeaderChangeListener) listeners;
        return this;
    }

    /**
     * {@inheritDoc}
     * <p>Attaches the StickyHeaderHelper from the RecyclerView when necessary</p>
     *
     * @since 5.0.0-b6
     */
    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        if (mStickyHeaderHelper != null && headersShown) {
            mStickyHeaderHelper.attachToRecyclerView(mRecyclerView);
        }
    }

    /**
     * {@inheritDoc}
     * <p>Detaches the StickyHeaderHelper from the RecyclerView if necessary.</p>
     *
     * @since 5.0.0-b6
     */
    @Override
    public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
        if (mStickyHeaderHelper != null) {
            mStickyHeaderHelper.detachFromRecyclerView(mRecyclerView);
            mStickyHeaderHelper = null;
        }
        super.onDetachedFromRecyclerView(recyclerView);
    }

    /**
     * Maps and expands items that are initially configured to be shown as expanded.
     * <p>This method should be called during the creation of the Activity/Fragment, useful also
     * after a screen rotation.
     * <br/>It is also called after DataSet is updated.</p>
     * <b>Note: </b>Only items at level 0 are automatically expanded, ignored all sub-levels.
     *
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b6
     */
    public FlexibleAdapter expandItemsAtStartUp() {
        int position = 0;
        setAnimate(true);
        multiRange = true;
        while (position < mItems.size()) {
            T item = getItem(position);
            if (isExpanded(item)) {
                expand(position, false, true);
                if (!headersShown && isHeader(item) && !item.isHidden())
                    headersShown = true;
            }
            position++;
        }
        multiRange = false;
        setAnimate(false);
        return this;
    }

    /*------------------------------*/
    /* SELECTION METHODS OVERRIDDEN */
    /*------------------------------*/

    /**
     * Checks if the current item has the property {@code enabled = true}.
     *
     * @param position the current position of the item to check
     * @return true if the item property <i>enabled</i> is set true, false otherwise
     * @since 5.0.0-b6
     */
    public boolean isEnabled(int position) {
        T item = getItem(position);
        return item != null && item.isEnabled();
    }

    /**
     * {@inheritDoc}
     *
     * @since 5.0.0-b6
     */
    @Override
    public boolean isSelectable(int position) {
        T item = getItem(position);
        return item != null && item.isSelectable();
    }

    /**
     * {@inheritDoc}
     *
     * @param position Position of the item to toggle the selection status for.
     * @since 5.0.0-b1
     */
    @Override
    public void toggleSelection(@IntRange(from = 0) int position) {
        T item = getItem(position);
        //Allow selection only for selectable items
        if (item != null && item.isSelectable()) {
            IExpandable parent = getExpandableOf(item);
            boolean hasParent = parent != null;
            if ((isExpandable(item) || !hasParent) && !childSelected) {
                //Allow selection of Parent if no Child has been previously selected
                parentSelected = true;
                if (hasParent)
                    selectedLevel = parent.getExpansionLevel();
                super.toggleSelection(position);
            } else if (!parentSelected && hasParent && parent.getExpansionLevel() + 1 == selectedLevel
                    || selectedLevel == -1) {
                //Allow selection of Child of same level and if no Parent has been previously selected
                childSelected = true;
                selectedLevel = parent.getExpansionLevel() + 1;
                super.toggleSelection(position);
            }
        }

        //Reset flags if necessary, just to be sure
        if (getSelectedItemCount() == 0) {
            selectedLevel = -1;
            parentSelected = childSelected = false;
        }
    }

    /**
     * Helper to automatically select all the items of the viewType equal to the viewType of
     * the first selected item.
     * <p>Examples:
     * <br/>- if user initially selects an expandable of type A, then only expandable items of
     * type A will be selected.
     * <br/>- if user initially selects a non-expandable of type B, then only items of Type B
     * will be selected.
     * <br/>- The developer can override this behaviour by passing a list of viewTypes for which
     * he wants to force the selection.</p>
     *
     * @param viewTypes All the desired viewTypes to be selected, pass nothing to automatically
     *                  select all the viewTypes of the first item user selected
     * @since 5.0.0-b1
     */
    @Override
    public void selectAll(Integer... viewTypes) {
        if (getSelectedItemCount() > 0 && viewTypes.length == 0) {
            super.selectAll(getItemViewType(getSelectedPositions().get(0)));//Priority on the first item
        } else {
            super.selectAll(viewTypes);//Force the selection for the viewTypes passed
        }
    }

    /**
     * {@inheritDoc}
     *
     * @since 5.0.0-b1
     */
    @Override
    @CallSuper
    public void clearSelection() {
        parentSelected = childSelected = false;
        super.clearSelection();
    }

    /**
     * @return true if a parent is selected
     * @since 5.0.0-b1
     */
    public boolean isAnyParentSelected() {
        return parentSelected;
    }

    /**
     * @return true if any child of any parent is selected, false otherwise
     * @since 5.0.0-b1
     */
    public boolean isAnyChildSelected() {
        return childSelected;
    }

    /*--------------*/
    /* MAIN METHODS */
    /*--------------*/

    /**
     * Convenience method of {@link #updateDataSet(List, boolean)}.
     * <p>In this case changes will NOT be animated: {@link #notifyDataSetChanged()} will be invoked.</p>
     *
     * @param items the new data set
     * @see #updateDataSet(List, boolean)
     * @since 5.0.0-b1
     */
    @CallSuper
    public void updateDataSet(List<T> items) {
        updateDataSet(items, false);
    }

    /**
     * This method will refresh the entire DataSet content.
     * <p>Optionally all changes can be animated, limited by the value previously set with
     * {@link #setAnimateToLimit(int)} to improve performance on big list.</p>
     * Pass {@code animate = false} to directly invoke {@link #notifyDataSetChanged()}
     * without any animations.
     * <p><b>Note:</b> This methods calls {@link #expandItemsAtStartUp()} and
     * {@link #showAllHeaders()} if headers are shown.</p>
     *
     * @param items   the new data set
     * @param animate true to animate the changes, false for a quick refresh
     * @see #updateDataSet(List)
     * @see #setAnimateToLimit(int)
     * @since 5.0.0-b7 Created
     * <br/>5.0.0-b8 Synchronization animations limit
     */
    @CallSuper
    public void updateDataSet(@Nullable List<T> items, boolean animate) {
        if (animate) {
            mHandler.removeMessages(UPDATE);
            mHandler.sendMessage(Message.obtain(mHandler, UPDATE, items));
        } else {
            if (items == null)
                mItems = new ArrayList<>();
            else
                mItems = new ArrayList<>(items);
            postUpdate(true);
        }
    }

    /**
     * Returns the custom object "Item".
     * <p>This cannot be overridden since the entire library relies on it.</p>
     *
     * @param position the position of the item in the list
     * @return The custom "Item" object or null if item not found
     * @since 1.0.0
     */
    public final T getItem(@IntRange(from = 0) int position) {
        if (position < 0 || position >= mItems.size())
            return null;
        return mItems.get(position);
    }

    /**
     * @param position the position of the current item
     * @return Hashcode of the item at the specific position
     * @since 5.0.0-b1
     */
    @Override
    public long getItemId(int position) {
        T item = getItem(position);
        return item != null ? item.hashCode() : RecyclerView.NO_ID;
    }

    /**
     * This cannot be overridden since the selection relies on it.
     *
     * @return the total number of the items currently displayed by the adapter
     * @see #getItemCountOfTypes(Integer...)
     * @see #getItemCountOfTypesUntil(int, Integer...)
     * @see #isEmpty()
     * @since 1.0.0
     */
    @Override
    public final int getItemCount() {
        return mItems != null ? mItems.size() : 0;
    }

    /**
     * Provides the number of items currently displayed of one or more certain types.
     *
     * @param viewTypes the viewTypes to count
     * @return number of the viewTypes counted
     * @see #getItemCount()
     * @see #getItemCountOfTypesUntil(int, Integer...)
     * @see #isEmpty()
     * @since 5.0.0-b1
     */
    public int getItemCountOfTypes(Integer... viewTypes) {
        return getItemCountOfTypesUntil(getItemCount(), viewTypes);
    }

    /**
     * Provides the number of items currently displayed of one or more certain types until
     * the specified position.
     *
     * @param position  the position limit where to stop counting (included)
     * @param viewTypes the viewTypes to count
     * @see #getItemCount()
     * @see #getItemCountOfTypes(Integer...)
     * @see #isEmpty()
     * @since 5.0.0-b5
     */
    //TODO: deprecation?
    public int getItemCountOfTypesUntil(@IntRange(from = 0) int position, Integer... viewTypes) {
        List<Integer> viewTypeList = Arrays.asList(viewTypes);
        int count = 0;
        for (int i = 0; i < position; i++) {
            //Privilege faster counting if autoMap is active
            if ((autoMap && viewTypeList.contains(mItems.get(i).getLayoutRes()))
                    || viewTypeList.contains(getItemViewType(i)))
                count++;
        }
        return count;
    }

    /**
     * You can override this method to define your own concept of "Empty". This method is never
     * called internally.
     *
     * @return true if the list is empty, false otherwise
     * @see #getItemCount()
     * @see #getItemCountOfTypes(Integer...)
     * @since 4.2.0
     */
    public boolean isEmpty() {
        return getItemCount() == 0;
    }

    /**
     * Retrieve the global position of the Item in the Adapter list.
     *
     * @param item the item to find
     * @return the global position in the Adapter if found, -1 otherwise
     * @since 5.0.0-b1
     */
    public int getGlobalPositionOf(@NonNull IFlexible item) {
        return item != null && mItems != null && !mItems.isEmpty() ? mItems.indexOf(item) : -1;
    }

    /**
     * This method is never called internally.
     *
     * @param item the item to find
     * @return true if the provided item is currently displayed, false otherwise
     * @since 2.0.0
     */
    public boolean contains(@NonNull T item) {
        return item != null && mItems != null && mItems.contains(item);
    }

    /**
     * New method to extract the new position where the item should lay.
     * <p><b>Note: </b>The Comparator should be customized to support <u>all</u> the types of items
     * this Adapter is displaying or a ClassCastException will be raised.</p>
     * If Comparator is {@code null} the returned position is 0.
     *
     * @param item       the item to evaluate the insertion
     * @param comparator the Comparator object with the logic to sort the list
     * @return the position resulted from sorting with the provided Comparator
     * @since 5.0.0-b7
     */
    public int calculatePositionFor(@NonNull Object item, @Nullable Comparator comparator) {
        //There's nothing to compare
        if (comparator == null)
            return 0;

        //Header is visible
        if (item instanceof ISectionable) {
            IHeader header = ((ISectionable) item).getHeader();
            if (header != null && !header.isHidden()) {
                List sortedList = getSectionItems(header);
                sortedList.add(item);
                Collections.sort(sortedList, comparator);
                int itemPosition = mItems.indexOf(item);
                int headerPosition = getGlobalPositionOf(header);
                //#143 - calculatePositionFor() missing a +1 when addItem (fixed by condition: itemPosition != -1)
                //fix represents the situation when item is before the target position (used in moveItem)
                int fix = itemPosition != -1 && itemPosition < headerPosition ? 0 : 1;
                int result = headerPosition + sortedList.indexOf(item) + fix;
                if (DEBUG) {
                    Log.v(TAG, "Calculated finalPosition=" + result + " sectionPosition=" + headerPosition
                            + " relativePosition=" + sortedList.indexOf(item) + " fix=" + fix);
                }
                return result;
            }
        }
        //All other cases
        List sortedList = new ArrayList(mItems);
        if (!sortedList.contains(item))
            sortedList.add(item);
        Collections.sort(sortedList, comparator);
        if (DEBUG)
            Log.v(TAG, "Calculated position " + Math.max(0, sortedList.indexOf(item)) + " for item=" + item);
        return Math.max(0, sortedList.indexOf(item));
    }

    /*--------------------------*/
    /* HEADERS/SECTIONS METHODS */
    /*--------------------------*/

    /**
     * @return true if orphan headers will be removed when unlinked, false if are kept unlinked
     * @see #setRemoveOrphanHeaders(boolean)
     * @since 5.0.0-b6
     */
    //TODO: deprecation?
    public boolean isRemoveOrphanHeaders() {
        return removeOrphanHeaders;
    }

    /**
     * Sets if the orphan headers will be deleted as well during the removal process.
     * <p>Default value is {@code false}.</p>
     *
     * @param removeOrphanHeaders true to remove the header during the remove items
     * @return this Adapter, so the call can be chained
     * @see #getOrphanHeaders()
     * @since 5.0.0-b6
     */
    //TODO: deprecation?
    public FlexibleAdapter setRemoveOrphanHeaders(boolean removeOrphanHeaders) {
        this.removeOrphanHeaders = removeOrphanHeaders;
        return this;
    }

    /**
     * Setting to automatically unlink the just deleted header from items having that header linked.
     * <p>Default value is {@code false}.</p>
     *
     * @param unlinkOnRemoveHeader true to unlink also all items with the just deleted header,
     *                             false otherwise
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b6
     */
    public FlexibleAdapter setUnlinkAllItemsOnRemoveHeaders(boolean unlinkOnRemoveHeader) {
        this.unlinkOnRemoveHeader = unlinkOnRemoveHeader;
        return this;
    }

    /**
     * Provides the list of the headers remained unlinked "orphan headers",
     * Orphan headers can appear from the user events (remove/move items).
     *
     * @return the list of the orphan headers collected until this moment
     * @see #setRemoveOrphanHeaders(boolean)
     * @since 5.0.0-b6
     */
    //TODO: deprecation?
    @NonNull
    public List<IHeader> getOrphanHeaders() {
        return mOrphanHeaders;
    }

    /**
     * Adds or overwrites the header for the passed Sectionable item.
     * <p>The header will be automatically displayed if all headers are currently shown and the
     * header is currently hidden, otherwise it will be kept hidden.</p>
     *
     * @param item   the item that holds the header
     * @param header the header item
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b6
     */
    //TODO: deprecation?
    public FlexibleAdapter linkHeaderTo(@NonNull T item, @NonNull IHeader header) {
        linkHeaderTo(item, header, Payload.LINK);
        if (header.isHidden() && headersShown) {
            showHeaderOf(getGlobalPositionOf(item), item, false);
        }
        return this;
    }

    /**
     * Hides and completely removes the header from the Adapter and from the item that holds it.
     * <p>No undo is possible.</p>
     *
     * @param item the item that holds the header
     * @since 5.0.0-b6
     */
    //TODO: deprecation?
    public IHeader unlinkHeaderFrom(@NonNull T item) {
        IHeader header = unlinkHeaderFrom(item, Payload.UNLINK);
        if (header != null && !header.isHidden()) {
            hideHeaderOf(item);
        }
        return header;
    }

    /**
     * Retrieves all the header items.
     *
     * @return non-null list with all the header items
     * @since 5.0.0-b6
     */
    @NonNull
    public List<IHeader> getHeaderItems() {
        List<IHeader> headers = new ArrayList<>();
        for (T item : mItems) {
            if (isHeader(item))
                headers.add((IHeader) item);
        }
        return headers;
    }

    /**
     * @param item the item to check
     * @return true if the item is an instance of {@link IHeader} interface
     * @since 5.0.0-b6
     */
    public boolean isHeader(T item) {
        return item != null && item instanceof IHeader;
    }

    /**
     * Helper for the Adapter to check if an item holds a header
     *
     * @param item the identified item
     * @return true if the item holds a header, false otherwise
     * @since 5.0.0-b6
     */
    public boolean hasHeader(@NonNull T item) {
        return getHeaderOf(item) != null;
    }

    /**
     * Checks if the item has a header and that header is the same of the provided one.
     *
     * @param item   the item supposing having a header
     * @param header the header to compare
     * @return true if the item has a header and it is the same of the provided one, false otherwise
     * @since 5.0.0-b6
     */
    public boolean hasSameHeader(@NonNull T item, @NonNull IHeader header) {
        IHeader current = getHeaderOf(item);
        return current != null && header != null && current.equals(header);
    }

    /**
     * Provides the header of the specified Sectionable.
     *
     * @param item the ISectionable item holding a header
     * @return the header of the passed Sectionable, null otherwise
     * @since 5.0.0-b6
     */
    public IHeader getHeaderOf(@NonNull T item) {
        if (item != null && item instanceof ISectionable) {
            return ((ISectionable) item).getHeader();
        }
        return null;
    }

    /**
     * Retrieves the {@link IHeader} item of any specified position.
     *
     * @param position the item position
     * @return the IHeader item linked to the specified item position
     * @since 5.0.0-b6
     */
    //TODO: rename to getSectionByItemPosition?
    public IHeader getSectionHeader(@IntRange(from = 0) int position) {
        //Headers are not visible nor sticky
        if (!headersShown)
            return null;
        //When headers are visible and sticky, get the previous header
        for (int i = position; i >= 0; i--) {
            T item = getItem(i);
            if (isHeader(item))
                return (IHeader) item;
        }
        return null;
    }

    /**
     * Retrieves the index of the specified header/section item.
     * <p>Counts the headers until this one.</p>
     *
     * @param header the header/section item
     * @return the index of the specified header/section
     * @since 5.0.0-b6
     */
    //TODO: deprecation?
    public int getSectionIndex(@NonNull IHeader header) {
        int position = getGlobalPositionOf(header);
        return getSectionIndex(position);
    }

    /**
     * Retrieves the header/section index of any specified position.
     * <p>Counts the headers until this one.</p>
     *
     * @param position any item position
     * @return the index of the specified item position
     * @since 5.0.0-b6
     */
    //TODO: deprecation?
    public int getSectionIndex(@IntRange(from = 0) int position) {
        int sectionIndex = 0;
        for (int i = 0; i <= position; i++) {
            if (isHeader(getItem(i)))
                sectionIndex++;
        }
        return sectionIndex;
    }

    /**
     * Provides all the items that belongs to the section represented by the specified header.
     *
     * @param header the header that represents the section
     * @return NonNull list of all items in the specified section.
     * @since 5.0.0-b6
     */
    @NonNull
    public List<ISectionable> getSectionItems(@NonNull IHeader header) {
        List<ISectionable> sectionItems = new ArrayList<>();
        int startPosition = getGlobalPositionOf(header);
        T item = getItem(++startPosition);
        while (hasSameHeader(item, header)) {
            sectionItems.add((ISectionable) item);
            item = getItem(++startPosition);
        }
        return sectionItems;
    }

    /**
     * Provides all the item positions that belongs to the section represented by the specified header.
     *
     * @param header the header that represents the section
     * @return NonNull list of all item positions in the specified section.
     * @since 5.0.0-b8
     */
    @NonNull
    public List<Integer> getSectionItemPositions(@NonNull IHeader header) {
        List<Integer> sectionItemPositions = new ArrayList<>();
        int startPosition = getGlobalPositionOf(header);
        T item = getItem(++startPosition);
        while (hasSameHeader(item, header)) {
            sectionItemPositions.add(++startPosition);
        }
        return sectionItemPositions;
    }

    /**
     * @return true if all headers are currently displayed, false otherwise
     * @since 5.0.0-b6
     */
    public boolean areHeadersShown() {
        return headersShown;
    }

    /**
     * Returns if Adapter will display sticky headers on the top.
     *
     * @return true if headers can be sticky, false if headers are scrolled together with all items
     * @since 5.0.0-b6
     */
    public boolean areHeadersSticky() {
        return headersSticky;
    }

    /**
     * Enables the sticky header functionality.
     * <p>Headers can be sticky only if they are shown. Command is otherwise ignored!</p>
     * <b>NOTE:</b>
     * <br/>- You must read {@link #getStickySectionHeadersHolder()}.
     * <br/>- Sticky headers are now clickable as any Views, but cannot be dragged nor swiped.
     * <br/>- Content and linkage are automatically updated.
     *
     * @return this Adapter, so the call can be chained
     * @see #getStickySectionHeadersHolder()
     * @since 5.0.0-b6
     */
    //TODO: deprecation use setStickyHeaders(true)?
    public FlexibleAdapter enableStickyHeaders() {
        return setStickyHeaders(true);
    }

    /**
     * Disables the sticky header functionality.
     *
     * @since 5.0.0-b6
     */
    //TODO: deprecation use setStickyHeaders(false)?
    public void disableStickyHeaders() {
        setStickyHeaders(false);
    }

    private FlexibleAdapter setStickyHeaders(final boolean sticky) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                // Add or Remove the sticky headers
                if (sticky) {
                    headersSticky = true;
                    if (mStickyHeaderHelper == null)
                        mStickyHeaderHelper = new StickyHeaderHelper(FlexibleAdapter.this,
                                mStickyHeaderChangeListener);
                    if (!mStickyHeaderHelper.isAttachedToRecyclerView())
                        mStickyHeaderHelper.attachToRecyclerView(mRecyclerView);
                    if (DEBUG)
                        Log.i(TAG, "Sticky headers enabled");
                } else if (mStickyHeaderHelper != null) {
                    headersSticky = false;
                    mStickyHeaderHelper.detachFromRecyclerView(mRecyclerView);
                    mStickyHeaderHelper = null;
                    if (DEBUG)
                        Log.i(TAG, "Sticky headers disabled");
                }
            }
        });
        return this;
    }

    /**
     * Returns the ViewGroup (FrameLayout) that will hold the headers when sticky.
     * <p><b>INCLUDE</b> the predefined layout after the RecyclerView widget, example:
     * <pre>&lt;android.support.v7.widget.RecyclerView
     * android:id="@+id/recycler_view"
     * android:layout_width="match_parent"
     * android:layout_height="match_parent"/&gt;</pre>
     * <pre>&lt;include layout="@layout/sticky_header_layout"/&gt;</pre></p>
     * <p><b>OR</b></p>
     * Implement this method to return an already inflated ViewGroup.
     * <br/>The ViewGroup <u>must</u> have {@code android:id="@+id/sticky_header_container"}.
     *
     * @return ViewGroup layout that will hold the sticky header ItemViews
     * @since 5.0.0-b6
     */
    public ViewGroup getStickySectionHeadersHolder() {
        return (ViewGroup) Utils.scanForActivity(mRecyclerView.getContext())
                .findViewById(R.id.sticky_header_container);
    }

    /**
     * Sets if all headers should be shown at startup. 2 effects are possible depending by
     * the current flag of scrolling animation:
     * <p>- if <b>enabled</b>, scrolling animations are performed as configured for the header item;
     * <br/>- if <b>disabled</b>, {@code notifyItemInserted()} is instead performed for each header
     * item.</p>
     * <b>Note:</b>
     * <br/>- {@code showAllHeaders()} is already called by this method.
     * <br/>- You should call this method before enabling sticky headers!
     * <p>Default value is {@code false} (headers are NOT shown at startup).</p>
     *
     * @param displayHeaders true to display headers, false to keep them hidden
     * @return this Adapter, so the call can be chained
     * @see #showAllHeaders()
     * @see #setAnimationOnScrolling(boolean)
     * @since 5.0.0-b6
     */
    //TODO: deprecation, rename to displayHeadersAtStartUp() with no parameters?
    public FlexibleAdapter setDisplayHeadersAtStartUp(boolean displayHeaders) {
        if (!headersShown && displayHeaders) {
            showAllHeaders(isAnimationOnScrollingEnabled());
        }
        return this;
    }

    /**
     * Shows all headers in the RecyclerView at their linked position. Not intended to be called at
     * startup.<br/> To display headers at startup please use {@code setDisplayHeadersAtStartUp()}
     * instead.
     * <p><b>Note:</b> Headers can only be shown or hidden all together.</p>
     *
     * @see #hideAllHeaders()
     * @see #setDisplayHeadersAtStartUp(boolean)
     * @since 5.0.0-b1
     */
    public void showAllHeaders() {
        showAllHeaders(false);
    }

    /**
     * @param init true to skip {@code notifyItemInserted}, false to make the call in Post and
     *             notify single insertion
     */
    private void showAllHeaders(boolean init) {
        if (init) {
            //No notifyItemInserted!
            showAllHeadersWithReset(true);
        } else {
            //In post, let's notifyItemInserted!
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    //#144 - Check if headers are already shown, discard the call to not duplicate headers
                    if (headersShown) {
                        Log.w(TAG,
                                "Headers already shown OR the method setDisplayHeadersAtStartUp() was already called!");
                        return;
                    }
                    showAllHeadersWithReset(false);
                    //#142 - At startup, when scrolling animation is disabled, insert notifications
                    // are performed to show headers for the first time. Header item is not visible
                    // at position 0: it has to be displayed by scrolling to it. This resolves the
                    // first item below sticky header when enabled as well.
                    if (mRecyclerView != null) {
                        int firstVisibleItem = Utils
                                .findFirstCompletelyVisibleItemPosition(mRecyclerView.getLayoutManager());
                        if (firstVisibleItem == 0 && isHeader(getItem(0)) && !isHeader(getItem(1))) {
                            mRecyclerView.scrollToPosition(0);
                        }
                    }
                }
            });
        }
    }

    /**
     * @param init true to skip the call to notifyItemInserted, false otherwise
     */
    private void showAllHeadersWithReset(boolean init) {
        multiRange = true;
        int position = 0;
        resetHiddenStatus();//Necessary after the filter and the update
        while (position < mItems.size()) {
            if (showHeaderOf(position, mItems.get(position), init))
                position++;//It's the same element, skip it
            position++;
        }
        headersShown = true;
        multiRange = false;
    }

    /**
     * Internal method to show/add a header in the internal list.
     *
     * @param position the position where the header will be displayed
     * @param item     the item that holds the header
     * @param init     for silent initialization
     * @since 5.0.0-b1
     */
    private boolean showHeaderOf(int position, @NonNull T item, boolean init) {
        //Take the header
        IHeader header = getHeaderOf(item);
        //Check header existence
        if (header == null || getPendingRemovedItem(item) != null)
            return false;
        if (header.isHidden()) {
            if (DEBUG)
                Log.v(TAG, "Showing header at position " + position + " header=" + header);
            header.setHidden(false);
            if (init) {//Skip notifyItemInserted!
                if (position < mItems.size()) {
                    mItems.add(position, (T) header);
                } else {
                    mItems.add((T) header);
                }
                return true;
            } else {
                return addItem(position, (T) header);
            }
        }
        return false;
    }

    /**
     * Hides all headers from the RecyclerView.
     * <p>Headers can be shown or hidden all together.</p>
     *
     * @see #showAllHeaders()
     * @since 5.0.0-b1
     */
    public void hideAllHeaders() {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                multiRange = true;
                //Hide orphan headers first
                for (IHeader header : getOrphanHeaders()) {
                    hideHeader(getGlobalPositionOf(header), header);
                }
                //Hide linked headers
                int position = mItems.size() - 1;
                while (position >= 0) {
                    T item = mItems.get(position);
                    if (isHeader(item))
                        hideHeader(position, (IHeader) item);
                    position--;
                }
                headersShown = false;
                setStickyHeaders(false);
                multiRange = false;
            }
        });
    }

    /**
     * Internal method to hide/remove a header from the internal list.
     *
     * @param item the item that holds the header
     * @since 5.0.0-b1
     */
    private boolean hideHeaderOf(@NonNull T item) {
        //Take the header
        IHeader header = getHeaderOf(item);
        //Check header existence
        return header != null && !header.isHidden() && hideHeader(getGlobalPositionOf(header), header);
    }

    private boolean hideHeader(int position, IHeader header) {
        if (position >= 0) {
            if (DEBUG)
                Log.v(TAG, "Hiding header at position " + position + " header=" + header);
            header.setHidden(true);
            //Remove and notify removals
            mItems.remove(position);
            notifyItemRemoved(position);
            return true;
        }
        return false;
    }

    /**
     * Helper method to ensure that all current headers are hidden before they are shown again.
     * <p>This method is already called inside {@link #showAllHeadersWithReset(boolean)}.</p>
     * This is necessary when {@link #setDisplayHeadersAtStartUp(boolean)} is set true and also
     * if an Activity/Fragment has been closed and then reopened. We need to reset hidden status,
     * the process is very fast.
     *
     * @since 5.0.0-b6
     */
    private void resetHiddenStatus() {
        for (T item : mItems) {
            IHeader header = getHeaderOf(item);
            if (header != null && !isExpandable((T) header))
                header.setHidden(true);
        }
    }

    /**
     * Internal method to link the header to the new item.
     * <p>Used by the Adapter during the Remove/Restore/Move operations.</p>
     * The new item looses the previous header, and if the old header is not shared,
     * old header is added to the orphan list.
     *
     * @param item    the item that holds the header
     * @param header  the header item
     * @param payload any non-null user object to notify the header and the item (the payload
     *                will be therefore passed to the bind method of the items ViewHolder),
     *                pass null to <u>not</u> notify the header and item
     * @since 5.0.0-b6
     */
    private boolean linkHeaderTo(@NonNull T item, @NonNull IHeader header, @Nullable Object payload) {
        boolean linked = false;
        if (item != null && item instanceof ISectionable) {
            ISectionable sectionable = (ISectionable) item;
            //Unlink header only if different
            if (sectionable.getHeader() != null && !sectionable.getHeader().equals(header)) {
                unlinkHeaderFrom((T) sectionable, Payload.UNLINK);
            }
            if (sectionable.getHeader() == null && header != null) {
                if (DEBUG)
                    Log.v(TAG, "Link header " + header + " to " + sectionable);
                sectionable.setHeader(header);
                linked = true;
                removeFromOrphanList(header);
                //Notify items
                if (payload != null) {
                    if (!header.isHidden())
                        notifyItemChanged(getGlobalPositionOf(header), payload);
                    if (!item.isHidden())
                        notifyItemChanged(getGlobalPositionOf(item), payload);
                }
            }
        } else {
            addToOrphanListIfNeeded(header, getGlobalPositionOf(item), 1);
            notifyItemChanged(getGlobalPositionOf(header), payload);
        }
        return linked;
    }

    /**
     * Internal method to unlink the header from the specified item.
     * <p>Used by the Adapter during the Remove/Restore/Move operations.</p>
     *
     * @param item    the item that holds the header
     * @param payload any non-null user object to notify the header and the item (the payload
     *                will be therefore passed to the bind method of the items ViewHolder),
     *                pass null to <u>not</u> notify the header and item
     * @since 5.0.0-b6
     */
    private IHeader unlinkHeaderFrom(@NonNull T item, @Nullable Object payload) {
        if (hasHeader(item)) {
            ISectionable sectionable = (ISectionable) item;
            IHeader header = sectionable.getHeader();
            if (DEBUG)
                Log.v(TAG, "Unlink header " + header + " from " + sectionable);
            sectionable.setHeader(null);
            addToOrphanListIfNeeded(header, getGlobalPositionOf(item), 1);
            //Notify items
            if (payload != null) {
                if (!header.isHidden())
                    notifyItemChanged(getGlobalPositionOf(header), payload);
                if (!item.isHidden())
                    notifyItemChanged(getGlobalPositionOf(item), payload);
            }
            return header;
        }
        return null;
    }

    //TODO: deprecation?
    private void addToOrphanListIfNeeded(IHeader header, int positionStart, int itemCount) {
        //Check if the header is not already added (happens after un-linkage with un-success linkage)
        if (!mOrphanHeaders.contains(header) && !isHeaderShared(header, positionStart, itemCount)) {
            mOrphanHeaders.add(header);
            if (DEBUG)
                Log.v(TAG, "Added to orphan list [" + mOrphanHeaders.size() + "] Header " + header);
        }
    }

    //TODO: deprecation?
    private void removeFromOrphanList(IHeader header) {
        if (mOrphanHeaders.remove(header) && DEBUG)
            Log.v(TAG, "Removed from orphan list [" + mOrphanHeaders.size() + "] Header " + header);
    }

    //TODO: deprecation?
    private boolean isHeaderShared(IHeader header, int positionStart, int itemCount) {
        int firstElementWithHeader = getGlobalPositionOf(header) + 1;
        for (int i = firstElementWithHeader; i < mItems.size(); i++) {
            T item = getItem(i);
            //Another header is met, we can stop here
            if (item instanceof IHeader)
                break;
            //Skip the items under modification
            if (i >= positionStart && i < positionStart + itemCount)
                continue;
            //An element with same header is met
            if (hasSameHeader(item, header))
                return true;
        }
        return false;
    }

    /*---------------------*/
    /* VIEW HOLDER METHODS */
    /*---------------------*/

    /**
     * Returns the ViewType for all Items depends by the current position.
     * <p>You can override this method to return specific values (don't call super) or you can
     * let this method to call the implementation of {@code IFlexible#getLayoutRes()} so ViewTypes
     * are automatically mapped (AutoMap).</p>
     *
     * @param position position for which ViewType is requested
     * @return if Item is found, any integer value from user layout resource if defined in
     * {@code IFlexible#getLayoutRes()}
     * @since 5.0.0-b1
     */
    @Override
    public int getItemViewType(int position) {
        T item = getItem(position);
        //Map the view type if not done yet
        mapViewTypeFrom(item);
        autoMap = true;
        return item.getLayoutRes();
    }

    /**
     * You can override this method to create ViewHolder from inside the Adapter or you can let
     * this method to call the implementation of {@code IFlexible#createViewHolder()} to create
     * ViewHolder from inside the Item (AutoMap).
     * <p/>{@inheritDoc}
     *
     * @return a new ViewHolder that holds a View of the given view type
     * @throws IllegalStateException if {@code IFlexible#createViewHolder()} is not implemented and
     *                               if this method is not overridden OR if ViewType instance has
     *                               not been correctly mapped.
     * @see IFlexible#createViewHolder(FlexibleAdapter, LayoutInflater, ViewGroup)
     * @since 5.0.0-b1
     */
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        T item = getViewTypeInstance(viewType);
        if (item == null) {
            //If everything has been set properly, this should never happen ;-)
            throw new IllegalStateException("ViewType instance has not been correctly mapped for viewType "
                    + viewType + " or AutoMap is not active: super() cannot be called.");
        }
        if (mInflater == null) {
            mInflater = LayoutInflater.from(parent.getContext());
        }
        return item.createViewHolder(this, mInflater, parent);
    }

    /**
     * You can override this method to bind the items into the corresponding ViewHolder from
     * inside the Adapter or you can let this method to call the implementation of
     * {@code IFlexible#bindViewHolder()} to bind the item inside itself (AutoMap).
     * <p/>{@inheritDoc}
     *
     * @throws IllegalStateException if {@code IFlexible#bindViewHolder()} is not implemented OR
     *                               if {@code super()} is called when AutoMap is not active.
     * @see IFlexible#bindViewHolder(FlexibleAdapter, RecyclerView.ViewHolder, int, List)
     * @since 5.0.0-b1
     */
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        this.onBindViewHolder(holder, position, Collections.unmodifiableList(new ArrayList<>()));
    }

    /**
     * Same concept of {@code #onBindViewHolder()} but with Payload.
     * <p/>{@inheritDoc}
     *
     * @throws IllegalStateException if {@code IFlexible#bindViewHolder()} is not implemented OR
     *                               if {@code super()} is called when AutoMap is not active.
     * @see IFlexible#bindViewHolder(FlexibleAdapter, RecyclerView.ViewHolder, int, List)
     * @see #onBindViewHolder(RecyclerView.ViewHolder, int)
     * @since 5.0.0-b1
     */
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {
        if (DEBUG) {
            Log.v(TAG, "onViewBound    Holder=" + holder.getClass().getSimpleName() + " position=" + position
                    + " itemId=" + holder.getItemId() + " layoutPosition=" + holder.getLayoutPosition());
        }
        //This check is necessary when using Expandable items, it helps to optimize binding.
        // Expandable items can lay out of the screen during the initialization/refresh
        // as soon as they are expanded one by one.
        //      if (holder.getLayoutPosition() > mRecyclerView.getChildCount()) {
        //         Log.w(TAG, "onViewBound    Skip binding for view out of screen " +
        //               holder.getLayoutPosition() + "/" + mRecyclerView.getChildCount());
        //         return;
        //      }
        if (!autoMap) {
            throw new IllegalStateException("AutoMap is not active: super() cannot be called.");
        }
        //When user scrolls, this line binds the correct selection status
        holder.itemView.setActivated(isSelected(position));
        //Bind the correct view elevation
        if (holder instanceof FlexibleViewHolder) {
            FlexibleViewHolder flexHolder = (FlexibleViewHolder) holder;
            float elevation = flexHolder.getActivationElevation();
            if (holder.itemView.isActivated() && elevation > 0)
                ViewCompat.setElevation(holder.itemView, elevation);
            else if (elevation > 0)//Leave unaltered the default elevation
                ViewCompat.setElevation(holder.itemView, 0);
        }
        //Bind the item
        T item = getItem(position);
        if (item != null) {
            holder.itemView.setEnabled(item.isEnabled());
            item.bindViewHolder(this, holder, position, payloads);
        }
        //Endless Scroll
        onLoadMore(position);
    }

    /*------------------------*/
    /* ENDLESS SCROLL METHODS */
    /*------------------------*/

    /**
     * Sets the ProgressItem to be displayed at the end of the list and activate the Loading More
     * functionality.
     * <p>Using this method, the {@link EndlessScrollListener} won't be called so that you can
     * handle a click event to load more items upon a user request.</p>
     * To correctly implement "Load more upon a user request" check the Wiki page of this library.
     *
     * @param progressItem the item representing the progress bar
     * @return this Adapter, so the call can be chained
     * @see #setEndlessScrollListener(EndlessScrollListener, IFlexible)
     * @since 5.0.0-b8
     */
    public FlexibleAdapter setEndlessProgressItem(@NonNull T progressItem) {
        if (progressItem != null) {
            setEndlessScrollThreshold(mEndlessScrollThreshold);
            progressItem.setEnabled(false);
            mProgressItem = progressItem;
        }
        return this;
    }

    /**
     * Sets the ProgressItem to be displayed at the end of the list and Sets the callback to
     * automatically load more items asynchronously (no further user action is needed but the
     * scroll).
     *
     * @param endlessScrollListener the callback to invoke the asynchronous loading
     * @param progressItem          the item representing the progress bar
     * @return this Adapter, so the call can be chained
     * @see #setEndlessProgressItem(IFlexible)
     * @since 5.0.0-b6
     */
    public FlexibleAdapter setEndlessScrollListener(@Nullable EndlessScrollListener endlessScrollListener,
            @NonNull T progressItem) {
        mEndlessScrollListener = endlessScrollListener;
        return setEndlessProgressItem(progressItem);
    }

    /**
     * Sets the minimum number of items still to bind to start the automatic loading.
     * <p>Default value is 1.</p>
     *
     * @param thresholdItems minimum number of unbound items to start loading more items
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b6
     */
    public FlexibleAdapter setEndlessScrollThreshold(@IntRange(from = 1) int thresholdItems) {
        //Increase visible threshold based on number of columns
        if (mRecyclerView != null) {
            int spanCount = getSpanCount(mRecyclerView.getLayoutManager());
            thresholdItems = thresholdItems * spanCount;
        }
        mEndlessScrollThreshold = thresholdItems;
        return this;
    }

    /**
     * This method is called automatically if METHOD A is implemented. If instead you chose the
     * classic way (METHOD B) to bind the items, you have to manually call this method at the end
     * of {@code onBindViewHolder()}.
     *
     * @param position the current binding position
     */
    protected void onLoadMore(int position) {
        if (mProgressItem != null && !mLoading && position >= getItemCount() - mEndlessScrollThreshold
                && getGlobalPositionOf(mProgressItem) < 0) {
            mLoading = true;
            mRecyclerView.post(new Runnable() {
                @Override
                public void run() {
                    mItems.add(mProgressItem);
                    notifyItemInserted(getItemCount());
                    if (mEndlessScrollListener != null)
                        mEndlessScrollListener.onLoadMore();
                }
            });
        }
    }

    /**
     * To call when more items are successfully loaded.
     * <p>When noMoreLoad OR onError OR onCancel, pass empty list or null to hide the
     * progressItem.</p>
     * In this case the ProgressItem is removed immediately.
     *
     * @param newItems the list of the new items, can be empty or null
     * @since 5.0.0-b6
     */
    public void onLoadMoreComplete(@Nullable List<T> newItems) {
        onLoadMoreComplete(newItems, 0L);
    }

    /**
     * To call to complete the action of the Loading more items.
     * <p>When noMoreLoad OR onError OR onCancel, pass empty list or null to hide the
     * progressItem.</p>
     * Optionally you can pass a delay time to still display the item with the latest information
     * inside. The message has to be handled inside the bindViewHolder of the item.
     *
     * @param newItems the list of the new items, can be empty or null
     * @param delay    the delay used to remove the progress item or -1 to disable the
     *                 loading forever and to keep the progress item.
     * @since 5.0.0-b8
     */
    public void onLoadMoreComplete(@Nullable List<T> newItems, @IntRange(from = -1) long delay) {
        //Handling the delay
        if (delay < 0) {
            //Disable the Endless functionality and keep the item
            mProgressItem = null;
        } else {
            //Delete the progress item with delay
            mHandler.sendEmptyMessageDelayed(LOAD_MORE_COMPLETE, delay);
        }
        //Add the new items or reset the loading status
        if (newItems != null && newItems.size() > 0) {
            if (DEBUG)
                Log.i(TAG, "onLoadMore performing adding " + newItems.size() + " new Items!");
            addItems(getItemCount(), newItems);
            //Reset OnLoadMore delayed
            mHandler.sendEmptyMessageDelayed(LOAD_MORE_RESET, 200L);
        } else {
            noMoreLoad();
        }
    }

    /**
     * Called when loading more should continue.
     */
    private void deleteProgressItem() {
        int progressPosition = getGlobalPositionOf(mProgressItem);
        if (progressPosition >= 0) {
            mItems.remove(mProgressItem);
            notifyItemRemoved(progressPosition);
        }
    }

    /**
     * Called when no more items are loaded.
     */
    private void noMoreLoad() {
        if (DEBUG)
            Log.v(TAG, "onLoadMore noMoreLoad!");
        notifyItemChanged(getItemCount() - 1, Payload.NO_MORE_LOAD);
        //Reset OnLoadMore delayed
        mHandler.sendEmptyMessageDelayed(LOAD_MORE_RESET, 200L);
    }

    private void resetOnLoadMore() {
        mLoading = false;
    }

    /*--------------------*/
    /* EXPANDABLE METHODS */
    /*--------------------*/

    /**
     * @return true if autoCollapseOnExpand is enabled, false otherwise
     * @since 5.0.0-b8
     */
    public boolean isAutoCollapseOnExpand() {
        return collapseOnExpand;
    }

    /**
     * Automatically collapse all previous expanded parents before expand the clicked parent.
     * <p>Default value is disabled.</p>
     *
     * @param collapseOnExpand true to collapse others items, false to just expand the current
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b1
     */
    public FlexibleAdapter setAutoCollapseOnExpand(boolean collapseOnExpand) {
        this.collapseOnExpand = collapseOnExpand;
        return this;
    }

    /**
     * @return true if autoScrollOnExpand is enabled, false otherwise
     * @since 5.0.0-b8
     */
    public boolean isAutoScrollOnExpand() {
        return scrollOnExpand;
    }

    /**
     * Automatically scroll the clicked expandable item to the first visible position.<br/>
     * Default value is disabled.
     * <p>This works ONLY in combination with {@link SmoothScrollLinearLayoutManager} or with
     * {@link SmoothScrollGridLayoutManager}.</p>
     *
     * @param scrollOnExpand true to enable automatic scroll, false to disable
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b1
     */
    public FlexibleAdapter setAutoScrollOnExpand(boolean scrollOnExpand) {
        this.scrollOnExpand = scrollOnExpand;
        return this;
    }

    /**
     * @param position the position of the item to check
     * @return true if the item implements {@link IExpandable} interface and its property has
     * {@code expanded = true}
     * @since 5.0.0-b1
     */
    public boolean isExpanded(@IntRange(from = 0) int position) {
        return isExpanded(getItem(position));
    }

    /**
     * @param item the item to check
     * @return true if the item implements {@link IExpandable} interface and its property has
     * {@code expanded = true}
     * @since 5.0.0-b1
     */
    public boolean isExpanded(@NonNull T item) {
        if (isExpandable(item)) {
            IExpandable expandable = (IExpandable) item;
            return expandable.isExpanded();
        }
        return false;
    }

    /**
     * @param item the item to check
     * @return true if the item implements {@link IExpandable} interface, false otherwise
     * @since 5.0.0-b1
     */
    public boolean isExpandable(@NonNull T item) {
        return item != null && item instanceof IExpandable;
    }

    /**
     * @return the level of the minium collapsible level used in MultiLevel expandable
     * @since 5.0.0-b6
     */
    public int getMinCollapsibleLevel() {
        return minCollapsibleLevel;
    }

    /**
     * Sets the minimum level which all sub expandable items will be collapsed too.
     * <p>Default value is {@link #minCollapsibleLevel} (All levels including 0).</p>
     *
     * @param minCollapsibleLevel the minimum level to auto-collapse sub expandable items
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b6
     */
    public FlexibleAdapter setMinCollapsibleLevel(int minCollapsibleLevel) {
        this.minCollapsibleLevel = minCollapsibleLevel;
        return this;
    }

    /**
     * Utility method to check if the expandable item has sub items.
     *
     * @param expandable the {@link IExpandable} object
     * @return true if the expandable has subItems, false otherwise
     * @since 5.0.0-b1
     */
    public boolean hasSubItems(@NonNull IExpandable expandable) {
        return expandable != null && expandable.getSubItems() != null && expandable.getSubItems().size() > 0;
    }

    /**
     * Retrieves the parent of a child.
     * <p>Only for a real child of an expanded parent.</p>
     *
     * @param position the position of the child item
     * @return the parent of this child item or null if item has no parent
     * @since 5.0.0-b1
     */
    public IExpandable getExpandableOf(@IntRange(from = 0) int position) {
        return getExpandableOf(getItem(position));
    }

    /**
     * Retrieves the parent of a child.
     * <p>Only for a real child of an expanded parent.</p>
     *
     * @param child the child item
     * @return the parent of this child item or null if item has no parent
     * @see #getExpandablePositionOf(IFlexible)
     * @see #getRelativePositionOf(IFlexible)
     * @since 5.0.0-b1
     */
    public IExpandable getExpandableOf(@NonNull T child) {
        for (T parent : mItems) {
            if (isExpandable(parent)) {
                IExpandable expandable = (IExpandable) parent;
                if (expandable.isExpanded() && hasSubItems(expandable)) {
                    List<T> list = expandable.getSubItems();
                    for (T subItem : list) {
                        //Pick up only no-hidden items
                        if (!subItem.isHidden() && subItem.equals(child))
                            return expandable;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Retrieves the parent position of a child.
     * <p>Only for a real child of an expanded parent.</p>
     *
     * @param child the child item
     * @return the parent position of this child item or -1 if not found
     * @see #getExpandableOf(IFlexible)
     * @see #getRelativePositionOf(IFlexible)
     * @since 5.0.0-b1
     */
    public int getExpandablePositionOf(@NonNull T child) {
        return getGlobalPositionOf(getExpandableOf(child));
    }

    /**
     * Provides the list where the child currently lays.
     *
     * @param child the child item
     * @return the list of the child element, or a new list if item
     * @see #getExpandableOf(IFlexible)
     * @see #getExpandablePositionOf(IFlexible)
     * @see #getRelativePositionOf(IFlexible)
     * @see #getExpandedItems()
     * @since 5.0.0-b1
     */
    @NonNull
    public List<T> getSiblingsOf(@NonNull T child) {
        IExpandable expandable = getExpandableOf(child);
        return expandable != null ? expandable.getSubItems() : new ArrayList<>();
    }

    /**
     * Retrieves the position of a child in the list where it lays.
     * <p>Only for a real child of an expanded parent.</p>
     *
     * @param child the child item
     * @return the position in the parent or -1 if, child is a parent itself or not found
     * @see #getExpandableOf(IFlexible)
     * @see #getExpandablePositionOf(IFlexible)
     * @since 5.0.0-b1
     */
    public int getRelativePositionOf(@NonNull T child) {
        return getSiblingsOf(child).indexOf(child);
    }

    /**
     * Provides a list of all expandable items that are currently expanded.
     *
     * @return a list with all expanded items
     * @see #getSiblingsOf(IFlexible)
     * @see #getExpandedPositions()
     * @since 5.0.0-b1
     */
    @NonNull
    public List<T> getExpandedItems() {
        List<T> expandedItems = new ArrayList<>();
        for (T item : mItems) {
            if (isExpanded(item))
                expandedItems.add(item);
        }
        return expandedItems;
    }

    /**
     * Provides a list of all expandable positions that are currently expanded.
     *
     * @return a list with the global positions of all expanded items
     * @see #getSiblingsOf(IFlexible)
     * @see #getExpandedItems()
     * @since 5.0.0-b1
     */
    @NonNull
    public List<Integer> getExpandedPositions() {
        List<Integer> expandedPositions = new ArrayList<>();
        for (int i = 0; i < mItems.size() - 1; i++) {
            if (isExpanded(mItems.get(i)))
                expandedPositions.add(i);
        }
        return expandedPositions;
    }

    /**
     * Expands an item that is IExpandable type, not yet expanded and if has subItems.
     * <p>If configured, automatic smooth scroll will be performed when necessary.</p>
     *
     * @param position the position of the item to expand
     * @return the number of subItems expanded
     * @see #expand(IFlexible)
     * @see #expand(IFlexible, boolean)
     * @see #expandAll()
     * @since 5.0.0-b1
     */
    public int expand(@IntRange(from = 0) int position) {
        return expand(position, false, false);
    }

    /**
     * Convenience method to expand a single item.
     * <p>Expands an item that is Expandable, not yet expanded, that has subItems and
     * no child is selected.</p>
     * If configured, automatic smooth scroll will be performed.
     *
     * @param item the item to expand, must be an Expandable and present in the list
     * @return the number of subItems expanded
     * @see #expand(int)
     * @see #expand(IFlexible, boolean)
     * @see #expandAll()
     * @since 5.0.0-b6
     */
    public int expand(T item) {
        return expand(getGlobalPositionOf(item), false, false);
    }

    /**
     * Convenience method to initially expand a single item.
     * <p><b>Note:</b> Must be used in combination with adding new items that require to be
     * initially expanded.</p>
     * <b>WARNING!</b>
     * <br/>Expanded status is ignored if {@code init = true}: it will always attempt to expand
     * the item: If subItems are already visible and the new item has status expanded, the
     * subItems will appear duplicated and the automatic smooth scroll will be skipped!
     *
     * @param item the item to expand, must be an Expandable and present in the list
     * @param init true to initially expand item
     * @return the number of subItems expanded
     * @see #expand(int)
     * @see #expand(IFlexible)
     * @see #expandAll()
     * @since 5.0.0-b7
     */
    public int expand(T item, boolean init) {
        return expand(getGlobalPositionOf(item), false, init);
    }

    private int expand(int position, boolean expandAll, boolean init) {
        T item = getItem(position);
        if (!isExpandable(item))
            return 0;

        IExpandable expandable = (IExpandable) item;
        if (!hasSubItems(expandable)) {
            expandable.setExpanded(false);//clear the expanded flag
            if (DEBUG)
                Log.w(TAG,
                        "No subItems to Expand on position " + position + " expanded " + expandable.isExpanded());
            return 0;
        }
        //      if (DEBUG && !init) {
        //         Log.v(TAG, "Request to Expand on position=" + position +
        //               " expanded=" + expandable.isExpanded() +
        //               " anyParentSelected=" + parentSelected);
        //      }
        int subItemsCount = 0;
        if (init || !expandable.isExpanded()
                && (!parentSelected || expandable.getExpansionLevel() <= selectedLevel)) {

            //Collapse others expandable if configured so Skip when expanding all is requested
            //Fetch again the new position after collapsing all!!
            if (collapseOnExpand && !expandAll && collapseAll(minCollapsibleLevel) > 0) {
                position = getGlobalPositionOf(item);
            }

            //Every time an expansion is requested, subItems must be taken from the
            // original Object and without the subItems marked hidden (removed)
            List<T> subItems = getExpandableList(expandable);
            mItems.addAll(position + 1, subItems);
            subItemsCount = subItems.size();
            //Save expanded state
            expandable.setExpanded(true);

            //Automatically smooth scroll the current expandable item to show as much
            // children as possible
            if (!init && scrollOnExpand && !expandAll) {
                autoScrollWithDelay(position, subItemsCount, 150L);
            }

            //Expand!
            notifyItemRangeInserted(position + 1, subItemsCount);
            //Show also the headers of the subItems
            if (!init && headersShown) {
                int count = 0;
                for (T subItem : subItems) {
                    if (showHeaderOf(position + (++count), subItem, false))
                        count++;
                }
            }
            if (DEBUG) {
                Log.i(TAG, (init ? "Initially expanded " : "Expanded ") + subItemsCount + " subItems on position="
                        + position);
            }
        }
        return subItemsCount;
    }

    /**
     * Expands all IExpandable items with minimum of level {@link #minCollapsibleLevel}.
     *
     * @return the number of parent successfully expanded
     * @see #expandAll(int)
     * @see #setMinCollapsibleLevel(int)
     * @since 5.0.0-b1
     */
    public int expandAll() {
        return expandAll(minCollapsibleLevel);
    }

    /**
     * Expands all IExpandable items with at least the specified level.
     *
     * @param level the minimum level to expand the sub expandable items
     * @return the number of parent successfully expanded
     * @see #expandAll()
     * @see #setMinCollapsibleLevel(int)
     * @since 5.0.0-b6
     */
    public int expandAll(int level) {
        int expanded = 0;
        //More efficient if we expand from First expandable position
        for (int i = 0; i < mItems.size(); i++) {
            T item = getItem(i);
            if (isExpandable(item)) {
                IExpandable expandable = (IExpandable) item;
                if (expandable.getExpansionLevel() <= level && expand(i, true, false) > 0) {
                    expanded++;
                }
            }
        }
        return expanded;
    }

    /**
     * Collapses an IExpandable item that is already expanded, if no subItem is selected.
     * <p>Multilevel behaviour: all IExpandable subItem, that are expanded, are recursively
     * collapsed.</p>
     *
     * @param position the position of the item to collapse
     * @return the number of subItems collapsed
     * @see #collapseAll()
     * @since 5.0.0-b1
     */
    public int collapse(@IntRange(from = 0) int position) {
        T item = getItem(position);
        if (!isExpandable(item))
            return 0;

        IExpandable expandable = (IExpandable) item;
        //Take the current subList (will improve the performance when collapseAll)
        List<T> subItems = getExpandableList(expandable);
        int subItemsCount = subItems.size(), recursiveCount = 0;

        if (DEBUG && hashItems == null) {
            Log.v(TAG, "Request to Collapse on position=" + position + " expanded=" + expandable.isExpanded()
                    + " hasSubItemsSelected=" + hasSubItemsSelected(position, subItems));
        }

        if (expandable.isExpanded() && subItemsCount > 0
                && (!hasSubItemsSelected(position, subItems) || getPendingRemovedItem(item) != null)) {

            //Recursive collapse of all sub expandable
            recursiveCount = recursiveCollapse(position + 1, subItems, expandable.getExpansionLevel());
            //Use HashSet (will improve the performance when collapseAll)
            if (hashItems != null)
                hashItems.removeAll(subItems);
            else
                mItems.removeAll(subItems);
            subItemsCount = subItems.size();
            //Save expanded state
            expandable.setExpanded(false);

            //Collapse!
            notifyItemRangeRemoved(position + 1, subItemsCount);
            //Hide also the headers of the subItems
            if (headersShown && !isHeader(item)) {
                for (T subItem : subItems) {
                    hideHeaderOf(subItem);
                }
            }
            if (DEBUG)
                Log.v(TAG, "Collapsed " + subItemsCount + " subItems on position " + position);
        }
        return subItemsCount + recursiveCount;
    }

    private int recursiveCollapse(int startPosition, List<T> subItems, int level) {
        int collapsed = 0;
        for (int i = 0; i < subItems.size(); i++) {
            T subItem = subItems.get(i);
            if (isExpanded(subItem)) {
                IExpandable expandable = (IExpandable) subItem;
                if (expandable.getExpansionLevel() >= level && collapse(startPosition + i) > 0) {
                    collapsed++;
                }
            }
        }
        return collapsed;
    }

    /**
     * Collapses all expandable items with the minimum level of {@link #minCollapsibleLevel}.
     *
     * @return the number of parent successfully collapsed
     * @see #collapse(int)
     * @see #collapseAll(int)
     * @see #setMinCollapsibleLevel(int)
     * @since 5.0.0-b1
     */
    public int collapseAll() {
        return collapseAll(minCollapsibleLevel);
    }

    /**
     * Collapses all expandable items with the level equals-higher than the specified level.
     *
     * @param level the level to start collapse sub expandable items
     * @return the number of parent successfully collapsed
     * @see #collapseAll()
     * @since 5.0.0-b6
     */
    public int collapseAll(int level) {
        hashItems = new LinkedHashSet<>(mItems);
        int collapsed = recursiveCollapse(0, mItems, level);
        mItems = new ArrayList<>(hashItems);
        hashItems = null;
        return collapsed;
    }

    /*----------------*/
    /* UPDATE METHODS */
    /*----------------*/

    /**
     * Updates/Rebounds the ItemView corresponding to the current position of that item, with
     * the new content provided.
     *
     * @param item    the item with the new content
     * @param payload any non-null user object to notify the current item (the payload will be
     *                therefore passed to the bind method of the item ViewHolder to optimize the
     *                content to update); pass null to rebind all fields of this item.
     * @since 2.1.0
     */
    public void updateItem(@NonNull T item, @Nullable Object payload) {
        updateItem(getGlobalPositionOf(item), item, payload);
    }

    /**
     * Updates/Rebounds the ItemView corresponding to the provided position with the new
     * provided content. Use {@link #updateItem(IFlexible, Object)} if the new content should
     * be bound on the same position.
     *
     * @param position the position where the new content should be updated and rebound
     * @param item     the item with the new content
     * @param payload  any non-null user object to notify the current item (the payload will be
     *                 therefore passed to the bind method of the item ViewHolder to optimize the
     *                 content to update); pass null to rebind all fields of this item.
     * @since 5.0.0-b1
     */
    public void updateItem(@IntRange(from = 0) int position, @NonNull T item, @Nullable Object payload) {
        if (position < 0 || position >= mItems.size()) {
            Log.e(TAG, "Cannot updateItem on position out of OutOfBounds!");
            return;
        }
        mItems.set(position, item);
        if (DEBUG)
            Log.d(TAG, "updateItem notifyItemChanged on position " + position);
        notifyItemChanged(position, payload);
    }

    /*----------------*/
    /* ADDING METHODS */
    /*----------------*/

    /**
     * Inserts the given Item at desired position or Add Item at last position with a delay
     * and auto-scroll to the position.
     * <p>Scrolling animation is automatically preserved, meaning that, notification for animation
     * is ignored.</p>
     * Useful at startup, when there's an item to add after Adapter Animations is completed.
     *
     * @param position             position of the item to add
     * @param item                 the item to add
     * @param delay                a non-negative delay
     * @param scrollToPosition     true if RecyclerView should scroll after item has been added,
     *                             false otherwise
     * @see #addItem(int, IFlexible)
     * @see #addItems(int, List)
     * @see #addSubItems(int, int, IExpandable, List, boolean, Object)
     * @see #removeItemWithDelay(IFlexible, long, boolean)
     * @since 5.0.0-b1
     */
    public void addItemWithDelay(@IntRange(from = 0) final int position, @NonNull final T item,
            @IntRange(from = 0) long delay, final boolean scrollToPosition) {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                setAnimate(true);
                if (addItem(position, item) && scrollToPosition && mRecyclerView != null) {
                    mRecyclerView.smoothScrollToPosition(Math.min(Math.max(0, position), getItemCount() - 1));
                }
                setAnimate(false);
            }
        }, delay);
    }

    /**
     * Inserts the given item in the internal list at the specified position or Adds the item
     * at last position.
     *
     * @param position position of the item to add
     * @param item     the item to add
     * @return true if the internal list was successfully modified, false otherwise
     * @see #addItemWithDelay(int, IFlexible, long, boolean)
     * @see #addItems(int, List)
     * @see #addSubItems(int, int, IExpandable, List, boolean, Object)
     * @since 1.0.0
     */
    public boolean addItem(@IntRange(from = 0) int position, @NonNull T item) {
        if (item == null) {
            Log.e(TAG, "No items to add!");
            return false;
        }
        if (DEBUG)
            Log.v(TAG, "addItem delegates addition to addItems!");
        List<T> items = new ArrayList<>(1);
        items.add(item);
        return addItems(position, items);
    }

    /**
     * Inserts a set of items in the internal list at specified position or Adds the items
     * at last position.
     * <p><b>NOTE:</b> When all headers are shown, the header (if exists) of this item will be
     * shown as well, unless it's already shown, so it will not be shown twice.</p>
     *
     * @param position position inside the list, -1 to add the set the end of the list
     * @param items    the items to add
     * @return true if the internal list was successfully modified, false otherwise
     * @see #addItem(int, IFlexible)
     * @see #addSubItems(int, int, IExpandable, List, boolean, Object)
     * @since 5.0.0-b1
     */
    public boolean addItems(@IntRange(from = 0) int position, @NonNull List<T> items) {
        if (position < 0) {
            Log.e(TAG, "Cannot addItems on negative position!");
            return false;
        }
        if (items == null || items.isEmpty()) {
            Log.e(TAG, "No items to add!");
            return false;
        }
        if (DEBUG)
            Log.d(TAG, "addItems on position=" + position + " itemCount=" + items.size());

        //Insert Items
        int initialCount = getItemCount();
        if (position < mItems.size()) {
            mItems.addAll(position, items);
        } else {
            mItems.addAll(items);
        }
        //Notify range addition
        notifyItemRangeInserted(position, items.size());

        //Show the headers of these items if all headers are already visible
        if (headersShown && !recursive) {
            recursive = true;
            for (T item : items)
                showHeaderOf(getGlobalPositionOf(item), item, false);//We have to find the correct position!
            recursive = false;
        }
        //Call listener to update EmptyView
        if (!recursive && mUpdateListener != null && !multiRange && initialCount == 0 && getItemCount() > 0)
            mUpdateListener.onUpdateEmptyView(getItemCount());
        return true;
    }

    /**
     * Convenience method of {@link #addSubItem(int, int, IFlexible, boolean, Object)}.
     * <br/>In this case parent item will never be notified nor expanded if it is collapsed.
     *
     * @return true if the internal list was successfully modified, false otherwise
     * @see #addSubItems(int, int, IExpandable, List, boolean, Object)
     * @since 5.0.0-b1
     */
    public boolean addSubItem(@IntRange(from = 0) int parentPosition, @IntRange(from = 0) int subPosition,
            @NonNull T item) {
        return this.addSubItem(parentPosition, subPosition, item, false, Payload.CHANGE);
    }

    /**
     * Convenience method of {@link #addSubItems(int, int, IExpandable, List, boolean, Object)}.
     * <br/>Optionally you can pass any payload to notify the parent about the change and optimize
     * the view binding.
     *
     * @param parentPosition position of the expandable item that shall contain the subItem
     * @param subPosition    the position of the subItem in the expandable list
     * @param item           the subItem to add in the expandable list
     * @param expandParent   true to initially expand the parent (if needed) and after to add
     *                       the subItem, false to simply add the subItem to the parent
     * @param payload        any non-null user object to notify the parent (the payload will be
     *                       therefore passed to the bind method of the parent ViewHolder),
     *                       pass null to <u>not</u> notify the parent
     * @return true if the internal list was successfully modified, false otherwise
     * @see #addSubItems(int, int, IExpandable, List, boolean, Object)
     * @since 5.0.0-b1
     */
    public boolean addSubItem(@IntRange(from = 0) int parentPosition, @IntRange(from = 0) int subPosition,
            @NonNull T item, boolean expandParent, @Nullable Object payload) {
        if (item == null) {
            Log.e(TAG, "No items to add!");
            return false;
        }
        //Build a new list with 1 item to chain the methods of addSubItems
        List<T> subItems = new ArrayList<>(1);
        subItems.add(item);
        //Reuse the method for subItems
        return addSubItems(parentPosition, subPosition, subItems, expandParent, payload);
    }

    /**
     * Adds all current subItems of the passed parent to the internal list.
     * <p><b>In order to add the subItems</b>, the following condition must be satisfied:
     * <br/>- The item resulting from the parent position is actually an {@link IExpandable}.</p>
     * Optionally the parent can be expanded and subItems displayed.
     * <br/>Optionally you can pass any payload to notify the parent about the change and optimize
     * the view binding.
     *
     * @param parentPosition position of the expandable item that shall contain the subItem
     * @param parent         the expandable item which shall contain the new subItem
     * @param expandParent   true to initially expand the parent (if needed) and after to add
     *                       the subItem, false to simply add the subItem to the parent
     * @param payload        any non-null user object to notify the parent (the payload will be
     *                       therefore passed to the bind method of the parent ViewHolder),
     *                       pass null to <u>not</u> notify the parent
     * @return true if the internal list was successfully modified, false otherwise
     * @see #addSubItems(int, int, IExpandable, List, boolean, Object)
     * @since 5.0.0-b1
     */
    public int addAllSubItemsFrom(@IntRange(from = 0) int parentPosition, @NonNull IExpandable parent,
            boolean expandParent, @Nullable Object payload) {
        List<T> subItems = getCurrentChildren(parent);
        addSubItems(parentPosition, 0, parent, subItems, expandParent, payload);
        return subItems.size();
    }

    /**
     * Convenience method of {@link #addSubItems(int, int, IExpandable, List, boolean, Object)}.
     * <br/>Optionally you can pass any payload to notify the parent about the change and optimize
     * the view binding.
     *
     * @param parentPosition position of the expandable item that shall contain the subItems
     * @param subPosition    the start position in the parent where the new items shall be inserted
     * @param items          the list of the subItems to add
     * @param expandParent   true to initially expand the parent (if needed) and after to add
     *                       the subItems, false to simply add the subItems to the parent
     * @param payload        any non-null user object to notify the parent (the payload will be
     *                       therefore passed to the bind method of the parent ViewHolder),
     *                       pass null to <u>not</u> notify the parent
     * @return true if the internal list was successfully modified, false otherwise
     * @see #addSubItems(int, int, IExpandable, List, boolean, Object)
     * @since 5.0.0-b1
     */
    public boolean addSubItems(@IntRange(from = 0) int parentPosition, @IntRange(from = 0) int subPosition,
            @NonNull List<T> items, boolean expandParent, @Nullable Object payload) {
        T parent = getItem(parentPosition);
        if (isExpandable(parent)) {
            IExpandable expandable = (IExpandable) parent;
            return addSubItems(parentPosition, subPosition, expandable, items, expandParent, payload);
        }
        Log.e(TAG, "Passed parentPosition doesn't belong to an Expandable item!");
        return false;
    }

    /**
     * Adds new subItems on the specified parent item, to the internal list.
     * <p><b>In order to add subItems</b>, the following condition must be satisfied:
     * <br/>- The item resulting from the parent position is actually an {@link IExpandable}.</p>
     * Optionally the parent can be expanded and subItems displayed.
     * <br/>Optionally you can pass any payload to notify the parent about the change and optimize
     * the view binding.
     *
     * @param parentPosition position of the expandable item that shall contain the subItems
     * @param subPosition    the start position in the parent where the new items shall be inserted
     * @param parent         the expandable item which shall contain the new subItem
     * @param items          the list of the subItems to add
     * @param expandParent   true to initially expand the parent (if needed) and after to add
     *                       the subItems, false to simply add the subItems to the parent
     * @param payload        any non-null user object to notify the parent (the payload will be
     *                       therefore passed to the bind method of the parent ViewHolder),
     *                       pass null to <u>not</u> notify the parent
     * @return true if the internal list was successfully modified, false otherwise
     * @see #addItems(int, List)
     * @since 5.0.0-b1
     */
    private boolean addSubItems(@IntRange(from = 0) int parentPosition, @IntRange(from = 0) int subPosition,
            @NonNull IExpandable parent, @NonNull List<T> items, boolean expandParent, @Nullable Object payload) {
        boolean added = false;
        //Expand parent if requested and not already expanded
        if (expandParent && !parent.isExpanded()) {
            expand(parentPosition);
        }
        //Notify the adapter of the new addition to display it and animate it.
        //If parent is collapsed there's no need to notify about the change.
        if (parent.isExpanded()) {
            added = addItems(parentPosition + 1 + Math.max(0, subPosition), items);
        }
        //Notify the parent about the change if requested
        if (payload != null)
            notifyItemChanged(parentPosition, payload);
        return added;
    }

    /**
     * Adds and shows an empty section to the top (position = 0).
     *
     * @return the calculated position for the new item
     * @see #addSection(IHeader, Comparator)
     * @since 5.0.0-b6
     */
    public int addSection(@NonNull IHeader header) {
        return addSection(header, (Comparator) null);
    }

    /**
     * @since 5.0.0-b6
     * @deprecated For a correct positioning of a new Section, use {@link #addSection(IHeader, Comparator)}
     * instead. This method doesn't perform any sort, so if the refHeader is unknown, the Header
     * is always inserted at the top, which doesn't cover all use cases.
     * <p>This method will be deleted before the final release.</p>
     */
    @Deprecated
    public void addSection(@NonNull IHeader header, @Nullable IHeader refHeader) {
        int position = 0;
        if (refHeader != null) {
            position = getGlobalPositionOf(refHeader) + 1;
            List<ISectionable> refSectionItems = getSectionItems(refHeader);
            if (!refSectionItems.isEmpty()) {
                position += refSectionItems.size();
            }
        }
        addItem(position, (T) header);
    }

    /**
     * Adds and shows an empty section.
     * <p>The new section is a {@link IHeader} item and the position is calculated after sorting
     * the Data Set.
     * <br/>- To add Sections to the <b>top</b>, set null the Comparator object or simply call
     * {@link #addSection(IHeader)};
     * <br/>- To add Sections to the <b>bottom</b> or in the <b>middle</b>, implement a Comparator
     * object able to support <u>all</u> the item types this Adapter is displaying or a
     * ClassCastException will be raised.
     *
     * @param header     the section header item to add
     * @param comparator the criteria to sort the Data Set used to extract the correct position
     *                   of the new header
     * @return the calculated position for the new item
     * @since 5.0.0-b7
     */
    public int addSection(@NonNull IHeader header, @Nullable Comparator comparator) {
        int position = calculatePositionFor(header, comparator);
        addItem(position, (T) header);
        return position;
    }

    /**
     * Adds a new item in a section when the relative position is <b>unknown</b>.
     * <p>The header can be a {@code IExpandable} type or {@code IHeader} type.</p>
     * The Comparator object must support <u>all</u> the item types this Adapter is displaying or
     * a ClassCastException will be raised.
     *
     * @param sectionable the item to add
     * @param header      the section receiving the new item
     * @param comparator  the criteria to sort the sectionItems used to extract the correct position
     *                    of the new item in the section
     * @return the calculated final position for the new item
     * @see #addItemToSection(ISectionable, IHeader, int)
     * @since 5.0.0-b6
     */
    public int addItemToSection(@NonNull ISectionable sectionable, @NonNull IHeader header,
            @NonNull Comparator comparator) {
        int index;
        if (header != null && !header.isHidden()) {
            List<ISectionable> sectionItems = getSectionItems(header);
            sectionItems.add(sectionable);
            //Sort the list for new position
            Collections.sort(sectionItems, comparator);
            //Get the new position
            index = sectionItems.indexOf(sectionable);
        } else {
            index = calculatePositionFor(sectionable, comparator);
        }
        return addItemToSection(sectionable, header, index);
    }

    /**
     * Adds a new item in a section when the relative position is <b>known</b>.
     * <p>The header can be a {@code IExpandable} type or {@code IHeader} type.</p>
     *
     * @param item   the item to add
     * @param header the section receiving the new item
     * @param index  the known relative position where to add the new item into the section
     * @return the calculated final position for the new item
     * @see #addItemToSection(ISectionable, IHeader, Comparator)
     * @since 5.0.0-b6
     */
    public int addItemToSection(@NonNull ISectionable item, @NonNull IHeader header,
            @IntRange(from = 0) int index) {
        if (DEBUG)
            Log.d(TAG, "addItemToSection relativePosition=" + index);
        int headerPosition = getGlobalPositionOf(header);
        if (index >= 0) {
            item.setHeader(header);
            if (headerPosition >= 0 && isExpandable((T) header))
                addSubItem(headerPosition, index, (T) item, false, Payload.SUB_ITEM);
            else
                addItem(headerPosition + 1 + index, (T) item);
        }
        //return the position
        return getGlobalPositionOf(item);
    }

    /*----------------------*/
    /* DELETE ITEMS METHODS */
    /*----------------------*/

    /**
     * @deprecated Param {@code resetLayoutAnimation} cannot be used anymore. Simply use
     * {@link #removeItemWithDelay(IFlexible, long, boolean)}
     */
    @Deprecated
    public void removeItemWithDelay(@NonNull final T item, @IntRange(from = 0) long delay, final boolean permanent,
            boolean resetLayoutAnimation) {
        Log.w(TAG,
                "Method removeItemWithDelay() with 'resetLayoutAnimation' has been deprecated, param 'resetLayoutAnimation'. Method will be removed with next release!");
        removeItemWithDelay(item, delay, permanent);
    }

    /**
     * Removes the given Item after the given delay.
     * <p>Scrolling animation is automatically preserved, meaning that notification for animation
     * is ignored.</p>
     *
     * @param item                 the item to add
     * @param delay                a non-negative delay
     * @param permanent            true to permanently delete the item (no undo), false otherwise
     * @see #removeItem(int)
     * @see #removeItems(List)
     * @see #removeItemsOfType(Integer...)
     * @see #removeRange(int, int)
     * @see #removeAllSelectedItems()
     * @see #addItemWithDelay(int, IFlexible, long, boolean)
     * @since 5.0.0-b7
     */
    public void removeItemWithDelay(@NonNull final T item, @IntRange(from = 0) long delay,
            final boolean permanent) {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                setAnimate(true);
                boolean tempPermanent = permanentDelete;
                if (permanent)
                    permanentDelete = true;
                removeItem(getGlobalPositionOf(item));
                permanentDelete = tempPermanent;
                setAnimate(false);
            }
        }, delay);
    }

    /**
     * Convenience method of {@link #removeItem(int, Object)} providing a null payload.
     *
     * @param position the position of item to remove
     * @see #removeItems(List)
     * @see #removeItemsOfType(Integer...)
     * @see #removeRange(int, int)
     * @see #removeAllSelectedItems()
     * @see #removeItemWithDelay(IFlexible, long, boolean)
     * @see #removeItem(int, Object)
     * @since 1.0.0
     */
    public void removeItem(@IntRange(from = 0) int position) {
        this.removeItem(position, Payload.CHANGE);
    }

    /**
     * Removes an item from the internal list and notify the change.
     * <p>The item is retained for an eventual Undo.</p>
     * This method delegates the removal to removeRange.
     *
     * @param position The position of item to remove
     * @param payload  any non-null user object to notify the parent (the payload will be
     *                 therefore passed to the bind method of the parent ViewHolder),
     *                 pass null to <u>not</u> notify the parent
     * @see #removeItems(List, Object)
     * @see #removeRange(int, int, Object)
     * @see #removeAllSelectedItems(Object)
     * @see #removeItem(int)
     * @since 5.0.0-b1
     */
    public void removeItem(@IntRange(from = 0) int position, @Nullable Object payload) {
        //Request to collapse after the notification of remove range
        collapse(position);
        if (DEBUG)
            Log.v(TAG, "removeItem delegates removal to removeRange");
        removeRange(position, 1, payload);
    }

    /**
     * Convenience method of {@link #removeItems(List, Object)} providing a null payload.
     *
     * @see #removeItem(int)
     * @see #removeItemsOfType(Integer...)
     * @see #removeRange(int, int)
     * @see #removeAllSelectedItems()
     * @see #removeItems(List, Object)
     * @since 1.0.0
     */
    public void removeItems(@NonNull List<Integer> selectedPositions) {
        this.removeItems(selectedPositions, Payload.CHANGE);
    }

    /**
     * Removes a list of items from internal list and notify the change.
     * <p>Every item is retained for an eventual Undo.</p>
     * Optionally you can pass any payload to notify the parent about the change and optimize the
     * view binding.
     * <p>This method delegates the removal to removeRange.</p>
     *
     * @param selectedPositions list with item positions to remove
     * @param payload           any non-null user object to notify the parent (the payload will be
     *                          therefore passed to the bind method of the parent ViewHolder),
     *                          pass null to <u>not</u> notify the parent
     * @see #removeItem(int, Object)
     * @see #removeRange(int, int, Object)
     * @see #removeAllSelectedItems(Object)
     * @see #removeItems(List)
     * @since 5.0.0-b1
     */
    public void removeItems(@NonNull List<Integer> selectedPositions, @Nullable Object payload) {
        if (DEBUG)
            Log.v(TAG, "removeItems selectedPositions=" + selectedPositions + " payload=" + payload);
        //Check if list is empty
        if (selectedPositions == null || selectedPositions.isEmpty())
            return;
        //Reverse-sort the list, start from last position for efficiency
        Collections.sort(selectedPositions, new Comparator<Integer>() {
            @Override
            public int compare(Integer lhs, Integer rhs) {
                return rhs - lhs;
            }
        });
        if (DEBUG)
            Log.v(TAG, "removeItems after reverse sort selectedPositions=" + selectedPositions);
        //Split the list in ranges
        int positionStart = 0, itemCount = 0;
        int lastPosition = selectedPositions.get(0);
        multiRange = true;
        for (Integer position : selectedPositions) {//10 9 8 //5 4 //1
            if (lastPosition - itemCount == position) {//10-0==10  10-1==9  10-2==8  10-3==5 NO  //5-1=4  5-2==1 NO
                itemCount++; // 1  2  3  //2
                positionStart = position;//10  9  8  //4
            } else {
                //Remove range
                if (itemCount > 0)
                    removeRange(positionStart, itemCount, payload);//8,3  //4,2
                positionStart = lastPosition = position;//5  //1
                itemCount = 1;
            }
            //Request to collapse after the notification of remove range
            collapse(position);
        }
        multiRange = false;
        //Remove last range
        if (itemCount > 0) {
            removeRange(positionStart, itemCount, payload);//1,1
        }
    }

    /**
     * Selectively removes all items of the type provided as parameter.
     *
     * @param viewTypes the viewTypes to remove
     * @see #removeItem(int, Object)
     * @see #removeItems(List)
     * @see #removeAllSelectedItems()
     * @since 5.0.0-b5
     */
    public void removeItemsOfType(Integer... viewTypes) {
        List<Integer> viewTypeList = Arrays.asList(viewTypes);
        List<Integer> itemsToRemove = new ArrayList<>();
        for (int i = mItems.size() - 1; i >= 0; i--) {
            //Privilege autoMap if active
            if ((autoMap && viewTypeList.contains(mItems.get(i).getLayoutRes()))
                    || viewTypeList.contains(getItemViewType(i)))
                itemsToRemove.add(i);
        }
        this.removeItems(itemsToRemove);
    }

    /**
     * Same as {@link #removeRange(int, int, Object)}, but in this case the parent will not be
     * notified about the change, if children are removed.
     *
     * @see #removeItem(int, Object)
     * @see #removeItems(List)
     * @see #removeItemsOfType(Integer...)
     * @see #removeAllSelectedItems()
     * @see #removeRange(int, int, Object)
     * @since 5.0.0-b1
     */
    public void removeRange(@IntRange(from = 0) int positionStart, @IntRange(from = 0) int itemCount) {
        this.removeRange(positionStart, itemCount, Payload.CHANGE);
    }

    /**
     * Removes a list of consecutive items from internal list and notify the change.
     * <p>If the item, resulting from the passed position:</p>
     * - is <u>not expandable</u> with <u>no</u> parent, it is removed as usual.<br/>
     * - is <u>not expandable</u> with a parent, it is removed only if the parent is expanded.<br/>
     * - is <u>expandable</u> implementing {@link IExpandable}, it is removed as usual, but
     * it will be collapsed if expanded.<br/>
     * - has a {@link IHeader} item, the header will be automatically linked to the first item
     * after the range or can remain orphan.
     * <p>Optionally you can pass any payload to notify the <u>parent</u> or the <u>header</u>
     * about the change and optimize the view binding.</p>
     *
     * @param positionStart the start position of the first item
     * @param itemCount     how many items should be removed
     * @param payload       any non-null user object to notify the parent (the payload will be
     *                      therefore passed to the bind method of the parent ViewHolder),
     *                      pass null to <u>not</u> notify the parent
     * @see #removeItem(int, Object)
     * @see #removeItems(List, Object)
     * @see #removeRange(int, int)
     * @see #removeAllSelectedItems(Object)
     * @see #setPermanentDelete(boolean)
     * @see #setRemoveOrphanHeaders(boolean)
     * @see #setRestoreSelectionOnUndo(boolean)
     * @see #getDeletedItems()
     * @see #getDeletedChildren(IExpandable)
     * @see #restoreDeletedItems()
     * @see #startUndoTimer(long, OnDeleteCompleteListener)
     * @see #emptyBin()
     * @since 5.0.0-b1
     */
    public void removeRange(@IntRange(from = 0) int positionStart, @IntRange(from = 0) int itemCount,
            @Nullable Object payload) {
        int initialCount = getItemCount();
        if (DEBUG)
            Log.d(TAG, "removeRange positionStart=" + positionStart + " itemCount=" + itemCount);
        if (positionStart < 0 || (positionStart + itemCount) > initialCount) {
            Log.e(TAG, "Cannot removeRange with positionStart out of OutOfBounds!");
            return;
        }

        //Handle header linkage
        IHeader header = getHeaderOf(getItem(positionStart));
        int headerPosition = getGlobalPositionOf(header);
        if (header != null && headerPosition >= 0) {
            //The header does not represents a group anymore, add it to the Orphan list
            addToOrphanListIfNeeded(header, positionStart, itemCount);
            notifyItemChanged(headerPosition, payload);
        }

        int parentPosition = -1;
        IExpandable parent = null;
        for (int position = positionStart; position < positionStart + itemCount; position++) {
            T item = getItem(positionStart);
            if (!permanentDelete) {
                //When removing a range of children, parent is always the same :-)
                if (parent == null)
                    parent = getExpandableOf(item);
                //Differentiate: (Expandable & NonExpandable with No parent) from (NonExpandable with a parent)
                if (parent == null) {
                    createRestoreItemInfo(positionStart, item, Payload.UNDO);
                } else {
                    parentPosition = createRestoreSubItemInfo(parent, item, Payload.UNDO);
                }
            }
            //Change to hidden status for section headers
            if (isHeader(item)) {
                header = (IHeader) item;
                header.setHidden(true);
            }
            //If item is a Header, remove linkage from ALL Sectionable items if exist
            if (unlinkOnRemoveHeader && isHeader(item)) {
                List<ISectionable> sectionableList = getSectionItems(header);
                for (ISectionable sectionable : sectionableList) {
                    sectionable.setHeader(null);
                    if (payload != null)
                        notifyItemChanged(getGlobalPositionOf(sectionable), Payload.UNLINK);
                }
            }
            //Remove item from internal list
            mItems.remove(positionStart);
            removeSelection(position);
        }

        //Notify removals
        if (parentPosition >= 0) {
            //Notify the Children removal only if Parent is expanded
            notifyItemRangeRemoved(positionStart, itemCount);
            //Notify the Parent about the change if requested
            if (payload != null)
                notifyItemChanged(parentPosition, payload);
        } else {
            //Notify range removal
            notifyItemRangeRemoved(positionStart, itemCount);
        }

        //Remove orphan headers
        if (removeOrphanHeaders) {
            for (IHeader orphanHeader : mOrphanHeaders) {
                headerPosition = getGlobalPositionOf(orphanHeader);
                if (headerPosition >= 0) {
                    if (DEBUG)
                        Log.v(TAG, "Removing orphan header " + orphanHeader);
                    if (!permanentDelete)
                        createRestoreItemInfo(headerPosition, (T) orphanHeader, Payload.UNDO);
                    mItems.remove(headerPosition);
                    notifyItemRemoved(headerPosition);
                }
            }
            mOrphanHeaders.clear();
        }

        //Update empty view
        if (mUpdateListener != null && !multiRange && initialCount > 0 && getItemCount() == 0)
            mUpdateListener.onUpdateEmptyView(getItemCount());
    }

    /**
     * Convenience method to remove all Items that are currently selected.
     * <p>Parent will not be notified about the change, if a child is removed.</p>
     *
     * @see #removeItem(int)
     * @see #removeItems(List)
     * @see #removeRange(int, int)
     * @see #removeItemsOfType(Integer...)
     * @see #removeAllSelectedItems(Object)
     * @since 5.0.0-b1
     */
    public void removeAllSelectedItems() {
        removeAllSelectedItems(null);
    }

    /**
     * Convenience method to remove all Items that are currently selected.<p>
     * Optionally the Parent can be notified about the change, if a child is removed, by passing
     * any payload.
     *
     * @param payload any non-null user object to notify the parent (the payload will be
     *                therefore passed to the bind method of the parent ViewHolder),
     *                pass null to <u>not</u> notify the parent
     * @see #removeItem(int, Object)
     * @see #removeItems(List, Object)
     * @see #removeRange(int, int, Object)
     * @see #removeAllSelectedItems()
     * @since 5.0.0-b1
     */
    public void removeAllSelectedItems(@Nullable Object payload) {
        this.removeItems(getSelectedPositions(), payload);
    }

    /*----------------------*/
    /* UNDO/RESTORE METHODS */
    /*----------------------*/

    /**
     * Returns if items will be deleted immediately when deletion is requested.
     * <p>Default value is {@code true} (Undo mechanism is disabled).</p>
     *
     * @return true if the items are deleted immediately, false if items are retained for an
     * eventual restoration
     * @since 5.0.0-b6
     */
    public boolean isPermanentDelete() {
        return permanentDelete;
    }

    /**
     * Sets if the deleted items should be deleted immediately or if Adapter should cache them to
     * restore them when requested by the user.
     * <p>Default value is {@code true} (Undo mechanism is disabled).</p>
     *
     * @param permanentDelete true to delete items forever, false to use the cache for Undo feature
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b6
     */
    public FlexibleAdapter setPermanentDelete(boolean permanentDelete) {
        this.permanentDelete = permanentDelete;
        return this;
    }

    /**
     * Returns the current configuration to restore selections on Undo.
     * <p>Default value is {@code false} (selection is NOT restored).</p>
     *
     * @return true if selection will be restored, false otherwise
     * @see #setRestoreSelectionOnUndo(boolean)
     * @since 5.0.0-b1
     */
    public boolean isRestoreWithSelection() {
        return restoreSelection;
    }

    /**
     * Gives the possibility to restore the selection on Undo, when {@link #restoreDeletedItems()}
     * is called.
     * <p>To use in combination with {@code ActionMode} in order to not disable it.</p>
     * Default value is {@code false} (selection is NOT restored).
     *
     * @param restoreSelection true to have restored items still selected, false to empty selections
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b1
     */
    public FlexibleAdapter setRestoreSelectionOnUndo(boolean restoreSelection) {
        this.restoreSelection = restoreSelection;
        return this;
    }

    /**
     * Restore items just removed.
     * <p><b>NOTE:</b> If filter is active, only items that match that filter will be shown(restored).</p>
     *
     * @see #setRestoreSelectionOnUndo(boolean)
     * @since 3.0.0
     */
    @SuppressWarnings("ResourceType")
    public void restoreDeletedItems() {
        stopUndoTimer();
        multiRange = true;
        int initialCount = getItemCount();
        //Selection coherence: start from a clear situation
        clearSelection();
        //Start from latest item deleted, since others could rely on it
        for (int i = mRestoreList.size() - 1; i >= 0; i--) {
            adjustSelected = false;
            RestoreInfo restoreInfo = mRestoreList.get(i);
            //Notify header if exists
            IHeader header = getHeaderOf(restoreInfo.item);
            if (header != null) {
                notifyItemChanged(getGlobalPositionOf(header), restoreInfo.payload);
            }

            if (restoreInfo.relativePosition >= 0) {
                //Restore child, if not deleted
                if (DEBUG)
                    Log.d(TAG, "Restore Child " + restoreInfo);
                //Skip subItem addition if filter is active
                if (hasSearchText() && !filterObject(restoreInfo.item, getSearchText()))
                    continue;
                //Check if refItem is shown, if not, show it again
                if (hasSearchText()
                        && getGlobalPositionOf(getHeaderOf(restoreInfo.item)) == RecyclerView.NO_POSITION) {
                    //Add parent + subItem
                    restoreInfo.refItem.setHidden(false);
                    addItem(restoreInfo.getRestorePosition(false), restoreInfo.refItem);
                    addSubItem(restoreInfo.getRestorePosition(true), 0, restoreInfo.item, true,
                            restoreInfo.payload);
                } else {
                    //Add subItem
                    addSubItem(restoreInfo.getRestorePosition(true), restoreInfo.relativePosition, restoreInfo.item,
                            false, restoreInfo.payload);
                }
            } else {
                //Restore parent or simple item, if not deleted
                if (DEBUG)
                    Log.d(TAG, "Restore Parent " + restoreInfo);
                //Skip item addition if filter is active
                if (hasSearchText() && !filterExpandableObject(restoreInfo.item))
                    continue;
                //Add header if not visible
                if (hasSearchText() && hasHeader(restoreInfo.item)
                        && getGlobalPositionOf(getHeaderOf(restoreInfo.item)) == RecyclerView.NO_POSITION)
                    getHeaderOf(restoreInfo.item).setHidden(true);
                //Add item
                addItem(restoreInfo.getRestorePosition(false), restoreInfo.item);
            }
            //Item is again visible
            restoreInfo.item.setHidden(false);

            //Restore header linkage
            if (unlinkOnRemoveHeader && isHeader(restoreInfo.item)) {
                header = (IHeader) restoreInfo.item;
                List<ISectionable> items = getSectionItems(header);
                for (ISectionable sectionable : items) {
                    linkHeaderTo((T) sectionable, header, restoreInfo.payload);
                }
            }
        }
        //Restore selection if requested, before emptyBin
        if (restoreSelection && !mRestoreList.isEmpty()) {
            if (isExpandable(mRestoreList.get(0).item) || getExpandableOf(mRestoreList.get(0).item) == null) {
                parentSelected = true;
            } else {
                childSelected = true;
            }
            for (RestoreInfo restoreInfo : mRestoreList) {
                if (restoreInfo.item.isSelectable()) {
                    addSelection(getGlobalPositionOf(restoreInfo.item));
                }
            }
            if (DEBUG)
                Log.d(TAG, "Selected positions after restore " + getSelectedPositions());
        }

        //Call listener to update EmptyView
        multiRange = false;
        if (mUpdateListener != null && initialCount == 0 && getItemCount() > 0)
            mUpdateListener.onUpdateEmptyView(getItemCount());

        emptyBin();
    }

    /**
     * Clean memory from items just removed.
     * <p><b>Note:</b> This method is automatically called after timer is over and after a
     * restoration.</p>
     *
     * @since 3.0.0
     */
    public synchronized void emptyBin() {
        if (DEBUG)
            Log.d(TAG, "emptyBin!");
        mRestoreList.clear();
    }

    /**
     * Convenience method to start Undo timer with default timeout of 5''
     *
     * @param listener the listener that will be called after timeout to commit the change
     * @since 3.0.0
     */
    public void startUndoTimer(OnDeleteCompleteListener listener) {
        startUndoTimer(0, listener);
    }

    /**
     * Start Undo timer with custom timeout
     *
     * @param timeout  custom timeout
     * @param listener the listener that will be called after timeout to commit the change
     * @since 3.0.0
     */
    public void startUndoTimer(long timeout, OnDeleteCompleteListener listener) {
        //Make longer the timer for new coming deleted items
        stopUndoTimer();
        mHandler.sendMessageDelayed(Message.obtain(mHandler, CONFIRM_DELETE, listener),
                timeout > 0 ? timeout : UNDO_TIMEOUT);
    }

    /**
     * Stop Undo timer.
     * <p><b>Note:</b> This method is automatically called in case of restoration.</p>
     *
     * @since 3.0.0
     */
    protected void stopUndoTimer() {
        mHandler.removeMessages(CONFIRM_DELETE);
    }

    /**
     * @return true if the restore list is not empty, false otherwise
     * @since 4.0.0
     */
    public boolean isRestoreInTime() {
        return mRestoreList != null && !mRestoreList.isEmpty();
    }

    /**
     * @return the list of deleted items
     * @since 4.0.0
     */
    @NonNull
    public List<T> getDeletedItems() {
        List<T> deletedItems = new ArrayList<>();
        for (RestoreInfo restoreInfo : mRestoreList) {
            deletedItems.add(restoreInfo.item);
        }
        return deletedItems;
    }

    /**
     * Retrieves the expandable of the deleted child.
     *
     * @param child the deleted child
     * @return the expandable(parent) of this child, or null if no parent found.
     * @since 5.0.0-b1
     */
    public IExpandable getExpandableOfDeletedChild(T child) {
        for (RestoreInfo restoreInfo : mRestoreList) {
            if (restoreInfo.item.equals(child) && isExpandable(restoreInfo.refItem))
                return (IExpandable) restoreInfo.refItem;
        }
        return null;
    }

    /**
     * Retrieves only the deleted children of the specified parent.
     *
     * @param expandable the parent item
     * @return the list of deleted children
     * @since 5.0.0-b1
     */
    @NonNull
    public List<T> getDeletedChildren(IExpandable expandable) {
        List<T> deletedChild = new ArrayList<>();
        for (RestoreInfo restoreInfo : mRestoreList) {
            if (restoreInfo.refItem != null && restoreInfo.refItem.equals(expandable)
                    && restoreInfo.relativePosition >= 0)
                deletedChild.add(restoreInfo.item);
        }
        return deletedChild;
    }

    /**
     * Retrieves all the original children of the specified parent, filtering out all the
     * deleted children if any.
     *
     * @param expandable the parent item
     * @return a non-null list of the original children minus the deleted children if some are
     * pending removal.
     * @since 5.0.0-b1
     */
    @NonNull
    public List<T> getCurrentChildren(@NonNull IExpandable expandable) {
        //Check item and subItems existence
        if (expandable == null || !hasSubItems(expandable))
            return new ArrayList<>();

        //Take a copy of the subItems list
        List<T> subItems = new ArrayList<>(expandable.getSubItems());
        //Remove all children pending removal
        if (!mRestoreList.isEmpty()) {
            subItems.removeAll(getDeletedChildren(expandable));
        }
        return subItems;
    }

    /*----------------*/
    /* FILTER METHODS */
    /*----------------*/

    /**
     * @return true if the current search text is not empty or null
     * @since 3.1.0
     */
    public boolean hasSearchText() {
        return mSearchText != null && !mSearchText.isEmpty();
    }

    /**
     * Checks if the searchText is changed.
     *
     * @param newText the new searchText
     * @return true if the old search text is different than the newText, false otherwise
     * @since 5.0.0-b5
     */
    public boolean hasNewSearchText(String newText) {
        return !mOldSearchText.equalsIgnoreCase(newText);
    }

    /**
     * @return the current search text
     * @since 3.1.0
     */
    public String getSearchText() {
        return mSearchText;
    }

    /**
     * Sets the new search text.
     *
     * @param searchText the new text to filter the items
     * @since 3.1.0
     */
    public void setSearchText(String searchText) {
        if (searchText != null)
            mSearchText = searchText.trim().toLowerCase(Locale.getDefault());
        else
            mSearchText = "";
    }

    /**
     * Sometimes it is necessary, while filtering or after the data set has been updated, to
     * rebound the items that remain unfiltered.
     * <p>If the items have highlighted text, those items must be refreshed in order to change the
     * text back to normal. This happens systematically when searchText is reduced in length by
     * the user.</p>
     * The notification is triggered in {@link #animateTo(List)} when new items are not added.
     * <p>Default value is {@code false}.</p>
     *
     * @param notifyChange true to trigger {@link #notifyItemChanged(int)} while filtering,
     *                     false otherwise
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b1
     */
    public final FlexibleAdapter setNotifyChangeOfUnfilteredItems(boolean notifyChange) {
        this.notifyChangeOfUnfilteredItems = notifyChange;
        return this;
    }

    /**
     * This method performs a further step to nicely animate the moved items.
     * <p>The process is very slow on big list of the order of ~3-5000 items and higher,
     * due to the calculation of the correct position for each item to be shifted.
     * Use with caution!</p>
     * The slowness is higher when the searchText is cleared out.
     * <p>Default value is {@code false}.</p>
     *
     * @param notifyMove true to animate move changes after filtering or update data set,
     *                   false otherwise
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b8
     */
    public final FlexibleAdapter setNotifyMoveOfFilteredItems(boolean notifyMove) {
        this.notifyMoveOfFilteredItems = notifyMove;
        return this;
    }

    /**
     * <b>WATCH OUT! PASS ALWAYS A <u>COPY</u> OF THE ORIGINAL LIST</b>: due to internal mechanism,
     * items are removed and/or added in order to animate items in the final list.
     * <p>Same as {@link #filterItems(List)}, but with a delay in the execution, useful to grab
     * more characters from user before starting the search.</p>
     *
     * @param unfilteredItems the list to filter
     * @param delay           any non-negative delay
     * @see #filterObject(IFlexible, String)
     * @see #setAnimateToLimit(int)
     * @since 5.0.0-b1
     * <br/>5.0.0-b8 Synchronization animations limit + AsyncFilter
     */
    public void filterItems(@NonNull List<T> unfilteredItems, @IntRange(from = 0) long delay) {
        //Make longer the timer for new coming deleted items
        mHandler.removeMessages(FILTER);
        mHandler.sendMessageDelayed(Message.obtain(mHandler, FILTER, unfilteredItems), delay > 0 ? delay : 0);
    }

    /**
     * <b>WATCH OUT! PASS ALWAYS A <u>COPY</u> OF THE ORIGINAL LIST</b>: due to internal
     * mechanism, items are removed and/or added in order to animate items in the final list.
     * <p>This method filters the provided list with the search text previously set with
     * {@link #setSearchText(String)}.</p>
     * <b>Important notes:</b>
     * <ol>
     * <li>This method calls {@link #filterObject(IFlexible, String)}.</li>
     * <li>If search text is empty or null, the provided list is the current list.</li>
     * <li>Any pending deleted items are always filtered out, but if restored, they will be
     * displayed according to the current filter and at the right positions.</li>
     * <li><b>NEW!</b> Expandable items are picked up and displayed if at least a child is
     * collected by the current filter.</li>
     * <li><b>NEW!</b> Items are animated thanks to {@link #animateTo(List)} BUT a limit of
     * {@value mAnimateToLimit} (default) items is set. <b>NOTE:</b> you can change this limit
     * by calling {@link #setAnimateToLimit(int)}. Above this limit {@link #notifyDataSetChanged()}
     * will be called to improve performance.</li>
     * </ol>
     *
     * @param unfilteredItems the list to filter
     * @see #filterObject(IFlexible, String)
     * @see #setAnimateToLimit(int)
     * @since 4.1.0 Created
     * <br/>5.0.0-b1 Expandable + Child filtering
     * <br/>5.0.0-b8 Synchronization animations limit + AsyncFilter
     */
    public void filterItems(@NonNull List<T> unfilteredItems) {
        mHandler.removeMessages(FILTER);
        mHandler.sendMessage(Message.obtain(mHandler, FILTER, unfilteredItems));
    }

    private synchronized void filterItemsAsync(@NonNull List<T> unfilteredItems) {
        // NOTE: In case user has deleted some items and he changes or applies a filter while
        // deletion is pending (Undo started), in order to be consistent, we need to recalculate
        // the new position in the new list and finally skip those items to avoid they are shown!

        if (DEBUG)
            Log.i(TAG, "filterItems with searchText=" + mSearchText);
        List<T> filteredItems = new ArrayList<>();
        filtering = true;//Enable flag: skip adjustPositions!

        if (hasSearchText()) {
            int newOriginalPosition = -1;
            for (T item : unfilteredItems) {
                if (mFilterAsyncTask != null && mFilterAsyncTask.isCancelled())
                    return;
                //Filter header first
                T header = (T) getHeaderOf(item);
                if (headersShown) {
                    if (header != null && filterObject(header, getSearchText())
                            && !filteredItems.contains(header)) {
                        filteredItems.add(header);
                    }
                }
                if (filterExpandableObject(item)) {
                    RestoreInfo restoreInfo = getPendingRemovedItem(item);
                    if (restoreInfo != null) {
                        //If found, point to the new reference while filtering
                        restoreInfo.filterRefItem = ++newOriginalPosition < filteredItems.size()
                                ? filteredItems.get(newOriginalPosition)
                                : null;
                    } else {
                        if (headersShown && hasHeader(item) && !filteredItems.contains(header)) {
                            filteredItems.add(header);
                        }
                        filteredItems.add(item);
                        newOriginalPosition += 1 + addFilteredSubItems(filteredItems, item);
                    }
                } else {
                    item.setHidden(true);
                }
            }
        } else if (hasNewSearchText(mSearchText)) {//this is better than checking emptiness
            filteredItems = unfilteredItems; //with no filter
            if (!mRestoreList.isEmpty()) {
                for (RestoreInfo restoreInfo : mRestoreList) {
                    //Clear the refItem generated by the filter
                    restoreInfo.clearFilterRef();
                    //Find the real reference
                    restoreInfo.refItem = filteredItems
                            .get(Math.max(0, filteredItems.indexOf(restoreInfo.item) - 1));
                }
                //Deleted items not yet committed should not appear
                filteredItems.removeAll(getDeletedItems());
            }
            resetFilterFlags(filteredItems);
        }

        //Reset flags
        filtering = false;

        //Animate search results only in case of new SearchText
        if (hasNewSearchText(mSearchText)) {
            mOldSearchText = mSearchText;
            animateTo(filteredItems);
        }
    }

    /**
     * This method is a wrapper filter for expandable items.<br/>
     * It performs filtering on the subItems returning true, if the any child should be in the
     * filtered collection.
     * <p>If the provided item is not an expandable it will be filtered as usual by
     * {@link #filterObject(T, String)}.</p>
     *
     * @param item the object with subItems to be inspected
     * @return true, if the object should be in the filteredResult, false otherwise
     * @since 5.0.0-b1
     */
    private boolean filterExpandableObject(T item) {
        //Reset expansion flag
        boolean filtered = false;
        if (isExpandable(item)) {
            IExpandable expandable = (IExpandable) item;
            //Save which expandable was originally expanded before filtering it out
            if (expandable.isExpanded()) {
                if (mExpandedFilterFlags == null)
                    mExpandedFilterFlags = new HashSet<>();
                mExpandedFilterFlags.add(expandable);
            }
            expandable.setExpanded(false);
            //Children scan filter
            for (T subItem : getCurrentChildren(expandable)) {
                //Reuse normal filter for Children
                subItem.setHidden(!filterObject(subItem, getSearchText()));
                if (!filtered && !subItem.isHidden()) {
                    filtered = true;
                }
            }
            //Expand if filter found text in subItems
            expandable.setExpanded(filtered);
        }
        //if not filtered already, fallback to Normal filter
        return filtered || filterObject(item, getSearchText());
    }

    /**
     * This method checks if the provided object is a type of {@link IFilterable} interface,
     * if yes, performs the filter on the implemented method {@link IFilterable#filter(String)}.
     * <p><b>NOTE:</b>
     * <br/>- The item will be collected if the implemented method returns true.
     * <br/>- {@code IExpandable} items are automatically picked up and displayed if at least a
     * child is collected by the current filter. You DON'T NEED to implement the scan for the
     * children: this is already done :-)
     * <br/>- If you don't want to implement the {@code IFilterable} interface on the items, then
     * you can override this method to have another filter logic!
     *
     * @param item       the object to be inspected
     * @param constraint constraint, that the object has to fulfil
     * @return true, if the object returns true as well, and so if it should be in the
     * filteredResult, false otherwise
     * @since 3.1.0 Created
     * <br/>5.0.0-b1 Expandable + Child filtering
     */
    protected boolean filterObject(T item, String constraint) {
        if (item instanceof IFilterable) {
            IFilterable filterable = (IFilterable) item;
            return filterable.filter(constraint);
        }
        return false;
    }

    /**
     * Adds to the final list also the filtered subItems.
     */
    private int addFilteredSubItems(List<T> values, T item) {
        if (isExpandable(item)) {
            IExpandable expandable = (IExpandable) item;
            if (hasSubItems(expandable)) {
                //Add subItems if not hidden by filterObject()
                List<T> filteredSubItems = new ArrayList<>();
                List<T> subItems = expandable.getSubItems();
                for (T subItem : subItems) {
                    if (!subItem.isHidden())
                        filteredSubItems.add(subItem);
                }
                values.addAll(filteredSubItems);
                return filteredSubItems.size();
            }
        }
        return 0;
    }

    /**
     * Clears flags after searchText is cleared out for Expandable items and sub items.
     */
    private void resetFilterFlags(List<T> items) {
        //Reset flags for all items!
        for (int i = 0; i < items.size(); i++) {
            T item = items.get(i);
            item.setHidden(false);
            if (isExpandable(item)) {
                IExpandable expandable = (IExpandable) item;
                //Reset expanded flag
                if (mExpandedFilterFlags != null)
                    expandable.setExpanded(mExpandedFilterFlags.contains(expandable));
                if (hasSubItems(expandable)) {
                    List<T> subItems = expandable.getSubItems();
                    for (T subItem : subItems) {
                        //Reset subItem hidden flag
                        subItem.setHidden(false);
                        //Show subItems for expanded items
                        if (expandable.isExpanded()) {
                            i++;
                            if (i < items.size())
                                items.add(i, subItem);
                            else
                                items.add(subItem);
                        }
                    }
                }
            }
        }
        mExpandedFilterFlags = null;
    }

    /**
     * Tunes the limit after the which the synchronization animations, occurred during
     * updateDataSet and filter operations, are skipped and {@link #notifyDataSetChanged()}
     * will be called instead.
     * <p>Default value is {@value mAnimateToLimit} items, number new items.</p>
     *
     * @param limit the number of new items that, when reached, will skip synchronization animations
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b8
     */
    public FlexibleAdapter setAnimateToLimit(int limit) {
        mAnimateToLimit = limit;
        return this;
    }

    /**
     * Animate the synchronization between the old list and the new list.
     * <p>Used by filter and updateDataSet.</p>
     * <b>Note:</b> The animations are skipped in favor of {@code notifyDataSetChanged}
     * when the number of items reaches the limit. See {@link #setAnimateToLimit(int)}.
     * <p><b>Note:</b> In case the animations are performed, unchanged items will be notified if
     * {@code notifyChangeOfUnfilteredItems} is set true, and payload will be set as a Boolean.</p>
     *
     * @param newItems the new list containing the new items
     * @see #setNotifyChangeOfUnfilteredItems(boolean)
     * @see #setNotifyMoveOfFilteredItems(boolean)
     * @see #setAnimateToLimit(int)
     * @since 5.0.0-b1 Created
     * <br>5.0.0-b8 Synchronization animation limit
     */
    public synchronized void animateTo(@Nullable List<T> newItems) {
        if (newItems == null)
            newItems = new ArrayList<>();
        notifications = new ArrayList<>();
        if (newItems.size() <= mAnimateToLimit) {
            if (DEBUG)
                Log.v(TAG, "Animate changes! oldSize=" + getItemCount() + " newSize=" + newItems.size() + " limit="
                        + mAnimateToLimit);
            List<T> tempItems = new ArrayList<>(mItems);
            applyAndAnimateRemovals(tempItems, newItems);
            applyAndAnimateAdditions(tempItems, newItems);
            if (notifyMoveOfFilteredItems)
                applyAndAnimateMovedItems(tempItems, newItems);
            mItems = tempItems;
        } else {
            if (DEBUG)
                Log.v(TAG, "NotifyDataSetChanged! oldSize=" + getItemCount() + " newSize=" + newItems.size()
                        + " limit=" + mAnimateToLimit);
            mItems = newItems;
            notifications.add(new Notification(-1, 0));
        }
        //Execute All notifications if filter was Synchronous!
        if (mFilterAsyncTask == null)
            executeNotifications();
    }

    /**
     * Find out all removed items and animate them, also update existent positions with newItems.
     *
     * @since 5.0.0-b1
     */
    private void applyAndAnimateRemovals(List<T> from, List<T> newItems) {
        //Using Hash for performance
        hashItems = new HashSet<>(newItems);
        int out = 0;
        for (int i = from.size() - 1; i >= 0; i--) {
            if (mFilterAsyncTask != null && mFilterAsyncTask.isCancelled())
                return;
            final T item = from.get(i);
            if (!hashItems.contains(item) && (!isHeader(item) || (isHeader(item) && headersShown))) {
                //if (DEBUG) Log.v(TAG, "calculateRemovals remove position=" + i + " item=" + item + " searchText=" + mSearchText);
                from.remove(i);
                notifications.add(new Notification(i, Notification.REMOVE));
                out++;
            } else if (notifyChangeOfUnfilteredItems) {
                from.set(i, item);
                notifications.add(new Notification(i, Notification.CHANGE));
                //if (DEBUG) Log.v(TAG, "calculateRemovals   keep position=" + i + " item=" + item + " searchText=" + mSearchText);
            }
        }
        hashItems = null;
        if (DEBUG)
            Log.v(TAG, "calculateRemovals total out=" + out);
    }

    /**
     * Find out all added items and animate them.
     *
     * @since 5.0.0-b1
     */
    private void applyAndAnimateAdditions(List<T> from, List<T> newItems) {
        //Using Hash for performance
        hashItems = new HashSet<>(from);
        int in = 0;
        for (int i = 0; i < newItems.size(); i++) {
            if (mFilterAsyncTask != null && mFilterAsyncTask.isCancelled())
                return;
            final T item = newItems.get(i);
            if (!hashItems.contains(item)) {
                //if (DEBUG) Log.v(TAG, "calculateAdditions    add position=" + i + " item=" + item + " searchText=" + mSearchText);
                if (notifyMoveOfFilteredItems) {
                    //We add always at the end to animate moved items at the missing position
                    from.add(item);
                    notifications.add(new Notification(from.size(), Notification.ADD));
                } else {
                    from.add(i, item);
                    notifications.add(new Notification(i, Notification.ADD));
                }
                in++;
            }
        }
        hashItems = null;
        if (DEBUG)
            Log.v(TAG, "calculateAdditions total new=" + in);
    }

    /**
     * Find out all moved items and animate them.
     * <p>This method is very slow on list bigger than ~3000 items. Use with caution!</p>
     *
     * @since 5.0.0-b7
     */
    private void applyAndAnimateMovedItems(List<T> from, List<T> newItems) {
        int move = 0;
        for (int toPosition = newItems.size() - 1; toPosition >= 0; toPosition--) {
            if (mFilterAsyncTask != null && mFilterAsyncTask.isCancelled())
                return;
            final T item = newItems.get(toPosition);
            final int fromPosition = from.indexOf(item);
            if (fromPosition >= 0 && fromPosition != toPosition) {
                //if (DEBUG) Log.v(TAG, "calculateMovedItems fromPosition=" + fromPosition + " toPosition=" + toPosition + " searchText=" + mSearchText);
                T movedItem = from.remove(fromPosition);
                if (toPosition < from.size())
                    from.add(toPosition, movedItem);
                else
                    from.add(movedItem);
                notifications.add(new Notification(fromPosition, toPosition, Notification.MOVE));
                move++;
            }
        }
        if (DEBUG)
            Log.v(TAG, "calculateMovedItems total move=" + move);
    }

    private synchronized void executeNotifications() {
        if (DEBUG)
            Log.i(TAG, "Performing " + notifications.size() + " notifications");
        setAnimate(false);//Disable scroll animation
        for (Notification notification : notifications) {
            switch (notification.operation) {
            case Notification.ADD:
                notifyItemInserted(notification.position);
                break;
            case Notification.CHANGE:
                notifyItemChanged(notification.position, Payload.FILTER);
                break;
            case Notification.REMOVE:
                notifyItemRemoved(notification.position);
                break;
            case Notification.MOVE:
                notifyItemMoved(notification.fromPosition, notification.position);
                break;
            default:
                if (DEBUG)
                    Log.w(TAG, "notifyDataSetChanged!");
                notifyDataSetChanged();
                break;
            }
        }
        notifications.clear();
    }

    /*---------------*/
    /* TOUCH METHODS */
    /*---------------*/

    /**
     * Used by {@link FlexibleViewHolder#onTouch(View, MotionEvent)}
     * to start Drag or Swipe when HandleView is touched.
     *
     * @return the ItemTouchHelper instance already initialized.
     * @since 5.0.0-b1
     */
    public final ItemTouchHelper getItemTouchHelper() {
        initializeItemTouchHelper();
        return mItemTouchHelper;
    }

    /**
     * Returns the customization of the ItemTouchHelperCallback.
     *
     * @return the ItemTouchHelperCallback instance already initialized
     * @since 5.0.0-b7
     */
    public final ItemTouchHelperCallback getItemTouchHelperCallback() {
        initializeItemTouchHelper();
        return mItemTouchHelperCallback;
    }

    /**
     * Returns whether ItemTouchHelper should start a drag and drop operation if an item is
     * long pressed.<p>
     * Default value is {@code false}.
     *
     * @return true if ItemTouchHelper should start dragging an item when it is long pressed,
     * false otherwise. Default value is {@code false}.
     * @since 5.0.0-b1
     */
    public final boolean isLongPressDragEnabled() {
        return mItemTouchHelperCallback != null && mItemTouchHelperCallback.isLongPressDragEnabled();
    }

    /**
     * Enable the Drag on LongPress on the entire ViewHolder.
     * <p><b>NOTE:</b> This will skip LongClick on the view in order to handle the LongPress,
     * however the LongClick listener will be called if necessary in the new
     * {@link FlexibleViewHolder#onActionStateChanged(int, int)}.</p>
     * Default value is {@code false}.
     *
     * @param longPressDragEnabled true to activate, false otherwise
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b1
     */
    public final FlexibleAdapter setLongPressDragEnabled(boolean longPressDragEnabled) {
        initializeItemTouchHelper();
        mItemTouchHelperCallback.setLongPressDragEnabled(longPressDragEnabled);
        return this;
    }

    /**
     * Enabled by default.
     * <p>To use, it is sufficient to set the HandleView by calling
     * {@link FlexibleViewHolder#setDragHandleView(View)}.</p>
     *
     * @return true if active, false otherwise
     * @since 5.0.0-b1
     */
    public final boolean isHandleDragEnabled() {
        return handleDragEnabled;
    }

    /**
     * Enable/Disable the drag with handle.
     * <p>Default value is {@code false}.</p>
     *
     * @param handleDragEnabled true to activate, false otherwise
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b1
     */
    public final FlexibleAdapter setHandleDragEnabled(boolean handleDragEnabled) {
        this.handleDragEnabled = handleDragEnabled;
        return this;
    }

    /**
     * Returns whether ItemTouchHelper should start a swipe operation if a pointer is swiped
     * over the View.
     * <p>Default value is {@code false}.</p>
     *
     * @return true if ItemTouchHelper should start swiping an item when user swipes a pointer
     * over the View, false otherwise. Default value is {@code false}.
     * @since 5.0.0-b1
     */
    public final boolean isSwipeEnabled() {
        return mItemTouchHelperCallback != null && mItemTouchHelperCallback.isItemViewSwipeEnabled();
    }

    /**
     * Enable the Full Swipe of the items.
     * <p>Default value is {@code false}.</p>
     *
     * @param swipeEnabled true to activate, false otherwise
     * @return this Adapter, so the call can be chained
     * @since 5.0.0-b1
     */
    public final FlexibleAdapter setSwipeEnabled(boolean swipeEnabled) {
        initializeItemTouchHelper();
        mItemTouchHelperCallback.setSwipeEnabled(swipeEnabled);
        return this;
    }

    /**
     * Moves the item placed at position {@code fromPosition} to the position
     * {@code toPosition}.
     * <br/>- Selection of moved element is preserved.
     * <br/>- If item is an expandable, it is collapsed and then expanded at the new position.
     *
     * @param fromPosition previous position of the item
     * @param toPosition   new position of the item
     * @see #moveItem(int, int, Object)
     * @since 5.0.0-b7
     */
    public void moveItem(int fromPosition, int toPosition) {
        moveItem(fromPosition, toPosition, Payload.MOVE);
    }

    /**
     * Moves the item placed at position {@code fromPosition} to the position
     * {@code toPosition}.
     * <br/>- Selection of moved element is preserved.
     * <br/>- If item is an expandable, it is collapsed and then expanded at the new position.
     *
     * @param fromPosition previous position of the item
     * @param toPosition   new position of the item
     * @param payload      allows to update the content of the item just moved
     * @since 5.0.0-b7
     */
    public void moveItem(int fromPosition, int toPosition, @Nullable Object payload) {
        if (DEBUG)
            Log.v(TAG, "moveItem fromPosition=" + fromPosition + " toPosition=" + toPosition);
        //Preserve selection
        if ((isSelected(fromPosition))) {
            removeSelection(fromPosition);
            addSelection(toPosition);
        }
        T item = mItems.get(fromPosition);
        //Preserve expanded status and Collapse expandable
        boolean expanded = isExpanded(item);
        if (expanded)
            collapse(toPosition);
        //Move item!
        mItems.remove(fromPosition);
        if (toPosition < getItemCount())
            mItems.add(toPosition, item);
        else
            mItems.add(item);
        notifyItemMoved(fromPosition, toPosition);
        if (payload != null)
            notifyItemChanged(toPosition, payload);
        //Eventually display the new Header
        if (headersShown) {
            showHeaderOf(toPosition, item, false);
        }
        //Restore original expanded status
        if (expanded)
            expand(toPosition);
    }

    /**
     * Swaps the elements of list at indices fromPosition and toPosition and notify the change.
     * <p>Selection of swiped elements is automatically updated.</p>
     *
     * @param fromPosition previous position of the item.
     * @param toPosition   new position of the item.
     * @since 5.0.0-b7
     */
    public void swapItems(List<T> list, int fromPosition, int toPosition) {
        if (fromPosition < 0 || fromPosition >= getItemCount() || toPosition < 0 || toPosition >= getItemCount()) {
            return;
        }
        if (DEBUG) {
            Log.v(TAG, "swapItems from=" + fromPosition + " [selected? " + isSelected(fromPosition) + "] to="
                    + toPosition + " [selected? " + isSelected(toPosition) + "]");
        }

        //Collapse expandable before swapping (otherwise items are mixed badly)
        if (fromPosition < toPosition && isExpandable(getItem(fromPosition)) && isExpanded(toPosition)) {
            //collapse(toPosition);
        }

        //Perform item swap (for all LayoutManagers)
        if (fromPosition < toPosition) {
            for (int i = fromPosition; i < toPosition; i++) {
                if (DEBUG)
                    Log.v(TAG, "swapItems from=" + i + " to=" + (i + 1));
                Collections.swap(mItems, i, i + 1);
                swapSelection(i, i + 1);
            }
        } else {
            for (int i = fromPosition; i > toPosition; i--) {
                if (DEBUG)
                    Log.v(TAG, "swapItems from=" + i + " to=" + (i - 1));
                Collections.swap(mItems, i, i - 1);
                swapSelection(i, i - 1);
            }
        }
        notifyItemMoved(fromPosition, toPosition);

        //Header swap linkage
        if (headersShown) {
            //Situation AFTER items have been swapped, items are inverted!
            T fromItem = getItem(toPosition);
            T toItem = getItem(fromPosition);
            int oldPosition, newPosition;
            if (toItem instanceof IHeader && fromItem instanceof IHeader) {
                if (fromPosition < toPosition) {
                    //Dragging down fromHeader
                    //Auto-linkage all section-items with new header
                    IHeader header = (IHeader) fromItem;
                    List<ISectionable> items = getSectionItems(header);
                    for (ISectionable sectionable : items) {
                        linkHeaderTo((T) sectionable, header, Payload.LINK);
                    }
                } else {
                    //Dragging up fromHeader
                    //Auto-linkage all section-items with new header
                    IHeader header = (IHeader) toItem;
                    List<ISectionable> items = getSectionItems(header);
                    for (ISectionable sectionable : items) {
                        linkHeaderTo((T) sectionable, header, Payload.LINK);
                    }
                }
            } else if (toItem instanceof IHeader) {
                //A Header is being swapped up
                //Else a Header is being swapped down
                oldPosition = fromPosition < toPosition ? toPosition + 1 : toPosition;
                newPosition = fromPosition < toPosition ? toPosition : fromPosition + 1;
                //Swap header linkage
                linkHeaderTo(getItem(oldPosition), getSectionHeader(oldPosition), Payload.LINK);
                linkHeaderTo(getItem(newPosition), (IHeader) toItem, Payload.LINK);
            } else if (fromItem instanceof IHeader) {
                //A Header is being dragged down
                //Else a Header is being dragged up
                oldPosition = fromPosition < toPosition ? fromPosition : fromPosition + 1;
                newPosition = fromPosition < toPosition ? toPosition + 1 : fromPosition;
                //Swap header linkage
                linkHeaderTo(getItem(oldPosition), getSectionHeader(oldPosition), Payload.LINK);
                linkHeaderTo(getItem(newPosition), (IHeader) fromItem, Payload.LINK);
            } else {
                //A Header receives the toItem
                //Else a Header receives the fromItem
                oldPosition = fromPosition < toPosition ? toPosition : fromPosition;
                newPosition = fromPosition < toPosition ? fromPosition : toPosition;
                //Swap header linkage
                T oldItem = getItem(oldPosition);
                IHeader header = getHeaderOf(oldItem);
                if (header != null) {
                    IHeader oldHeader = getSectionHeader(oldPosition);
                    if (oldHeader != null && !oldHeader.equals(header)) {
                        linkHeaderTo(oldItem, oldHeader, Payload.LINK);
                    }
                    linkHeaderTo(getItem(newPosition), header, Payload.LINK);
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     *
     * @since 5.0.0-b7
     */
    @Override
    public void onActionStateChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        if (mItemMoveListener != null)
            mItemMoveListener.onActionStateChanged(viewHolder, actionState);
        else if (mItemSwipeListener != null) {
            mItemSwipeListener.onActionStateChanged(viewHolder, actionState);
        }
    }

    /**
     * {@inheritDoc}
     *
     * @since 5.0.0-b1
     */
    @Override
    public boolean shouldMove(int fromPosition, int toPosition) {
        return (mItemMoveListener == null || mItemMoveListener.shouldMoveItem(fromPosition, toPosition));// &&
        //!(isExpandable(getItem(fromPosition)) && getExpandableOf(toPosition) != null);
    }

    /**
     * {@inheritDoc}
     *
     * @since 5.0.0-b1
     */
    @Override
    @CallSuper
    public boolean onItemMove(int fromPosition, int toPosition) {
        swapItems(mItems, fromPosition, toPosition);
        //After the swap, delegate further actions to the user
        if (mItemMoveListener != null) {
            mItemMoveListener.onItemMove(fromPosition, toPosition);
        }
        return true;
    }

    /**
     * {@inheritDoc}
     *
     * @since 5.0.0-b1
     */
    @Override
    @CallSuper
    public void onItemSwiped(int position, int direction) {
        //Delegate actions to the user
        if (mItemSwipeListener != null) {
            mItemSwipeListener.onItemSwipe(position, direction);
        }
    }

    private void initializeItemTouchHelper() {
        if (mItemTouchHelper == null) {
            if (mRecyclerView == null) {
                throw new IllegalStateException(
                        "RecyclerView cannot be null. Enabling LongPressDrag or Swipe must be done after the Adapter is added to the RecyclerView.");
            }
            mItemTouchHelperCallback = new ItemTouchHelperCallback(this);
            mItemTouchHelper = new ItemTouchHelper(mItemTouchHelperCallback);
            mItemTouchHelper.attachToRecyclerView(mRecyclerView);
        }
    }

    /*------------------------*/
    /* OTHERS PRIVATE METHODS */
    /*------------------------*/

    /**
     * Internal mapper to remember and add all types for the RecyclerView.
     *
     * @param item the item to map
     * @since 5.0.0-b1
     */
    private void mapViewTypeFrom(T item) {
        if (item != null && !mTypeInstances.containsKey(item.getLayoutRes())) {
            mTypeInstances.put(item.getLayoutRes(), item);
            if (DEBUG)
                Log.i(TAG, "Mapped viewType " + item.getLayoutRes() + " from " + item.getClass().getSimpleName());
        }
    }

    /**
     * Retrieves the TypeInstance remembered within the FlexibleAdapter for an item.
     *
     * @param viewType the ViewType of the item
     * @return the IFlexible instance, creator of the ViewType
     * @since 5.0.0-b1
     */
    private T getViewTypeInstance(int viewType) {
        return mTypeInstances.get(viewType);
    }

    /**
     * @param item the item to compare
     * @return the removed item if found, null otherwise
     */
    private RestoreInfo getPendingRemovedItem(T item) {
        for (RestoreInfo restoreInfo : mRestoreList) {
            //refPosition >= 0 means that position has been calculated and restore is ongoing
            if (restoreInfo.item.equals(item) && restoreInfo.refPosition < 0)
                return restoreInfo;
        }
        return null;
    }

    /**
     * @param expandable the expandable, parent of this sub item
     * @param item       the deleted item
     * @param payload    any payload object
     * @return the parent position
     * @since 5.0.0-b1
     */
    private int createRestoreSubItemInfo(IExpandable expandable, T item, @Nullable Object payload) {
        int parentPosition = getGlobalPositionOf(expandable);
        List<T> siblings = getExpandableList(expandable);
        int childPosition = siblings.indexOf(item);
        item.setHidden(true);
        mRestoreList.add(new RestoreInfo((T) expandable, item, childPosition, payload));
        if (DEBUG)
            Log.v(TAG, "Recycled Child " + mRestoreList.get(mRestoreList.size() - 1) + " with Parent position="
                    + parentPosition);
        return parentPosition;
    }

    /**
     * @param position the position of the item to retain.
     * @param item     the deleted item
     * @since 5.0.0-b1
     */
    private void createRestoreItemInfo(int position, T item, @Nullable Object payload) {
        //Collapse Parent before removal if it is expanded!
        if (isExpanded(item))
            collapse(position);
        item.setHidden(true);
        //Get the reference of the previous item (getItem returns null if outOfBounds)
        //If null, it will be restored at position = 0
        T refItem = getItem(position - 1);
        if (refItem != null) {
            //Check if the refItem is a child of an Expanded parent, take the parent!
            IExpandable expandable = getExpandableOf(refItem);
            if (expandable != null)
                refItem = (T) expandable;
        }
        mRestoreList.add(new RestoreInfo(refItem, item, payload));
        if (DEBUG)
            Log.v(TAG, "Recycled Parent " + mRestoreList.get(mRestoreList.size() - 1) + " on position=" + position);
    }

    /**
     * @param expandable the parent item
     * @return the list of the subItems not hidden
     * @since 5.0.0-b1
     */
    @NonNull
    private List<T> getExpandableList(IExpandable expandable) {
        List<T> subItems = new ArrayList<>();
        if (expandable != null && hasSubItems(expandable)) {
            List<T> allSubItems = expandable.getSubItems();
            for (T subItem : allSubItems) {
                //Pick up only no hidden items (doesn't get into account the filtered items)
                if (!subItem.isHidden())
                    subItems.add(subItem);
            }
        }
        return subItems;
    }

    /**
     * Allows or disallows the request to collapse the Expandable item.
     *
     * @param startPosition helps to improve performance, so we can avoid a new search for position
     * @param subItems      the list of sub items to check
     * @return true if at least 1 subItem is currently selected, false if no subItems are selected
     * @since 5.0.0-b1
     */
    private boolean hasSubItemsSelected(int startPosition, List<T> subItems) {
        for (T subItem : subItems) {
            if (isSelected(startPosition + 1) || (isExpandable(subItem)
                    && hasSubItemsSelected(startPosition + 1, getExpandableList((IExpandable) subItem))))
                return true;
        }
        return false;
    }

    private void autoScrollWithDelay(final int position, final int subItemsCount, final long delay) {
        //Must be delayed to give time at RecyclerView to recalculate positions after an automatic collapse
        new Handler(Looper.getMainLooper(), new Handler.Callback() {
            public boolean handleMessage(Message message) {
                int firstVisibleItem = Utils
                        .findFirstCompletelyVisibleItemPosition(mRecyclerView.getLayoutManager());
                int lastVisibleItem = Utils.findLastCompletelyVisibleItemPosition(mRecyclerView.getLayoutManager());
                int itemsToShow = position + subItemsCount - lastVisibleItem;
                //            if (DEBUG)
                //               Log.v(TAG, "autoScroll itemsToShow=" + itemsToShow + " firstVisibleItem=" + firstVisibleItem + " lastVisibleItem=" + lastVisibleItem + " RvChildCount=" + mRecyclerView.getChildCount());
                if (itemsToShow > 0) {
                    int scrollMax = position - firstVisibleItem;
                    int scrollMin = Math.max(0, position + subItemsCount - lastVisibleItem);
                    int scrollBy = Math.min(scrollMax, scrollMin);
                    int spanCount = getSpanCount(mRecyclerView.getLayoutManager());
                    if (spanCount > 1) {
                        scrollBy = scrollBy % spanCount + spanCount;
                    }
                    int scrollTo = firstVisibleItem + scrollBy;
                    //               if (DEBUG)
                    //                  Log.v(TAG, "autoScroll scrollMin=" + scrollMin + " scrollMax=" + scrollMax + " scrollBy=" + scrollBy + " scrollTo=" + scrollTo);
                    mRecyclerView.smoothScrollToPosition(scrollTo);
                } else if (position < firstVisibleItem) {
                    mRecyclerView.smoothScrollToPosition(position);
                }
                return true;
            }
        }).sendMessageDelayed(Message.obtain(mHandler), delay);
    }

    private void adjustSelected(int startPosition, int itemCount) {
        List<Integer> selectedPositions = getSelectedPositions();
        boolean adjusted = false;
        for (Integer position : selectedPositions) {
            if (position >= startPosition) {
                if (DEBUG)
                    Log.v(TAG, "Adjust Selected position " + position + " to "
                            + Math.max(position + itemCount, startPosition));
                removeSelection(position);
                addSelection(Math.max(position + itemCount, startPosition));
                adjusted = true;
            }
        }
        if (DEBUG && adjusted)
            Log.v(TAG, "AdjustedSelected=" + getSelectedPositions());
    }

    /*----------------*/
    /* INSTANCE STATE */
    /*----------------*/

    /**
     * Save the state of the current expanded items.
     *
     * @param outState Current state
     * @since 5.0.0-b1
     */
    public void onSaveInstanceState(Bundle outState) {
        if (outState != null) {
            //Save selection state
            super.onSaveInstanceState(outState);
            //Save selection coherence
            outState.putBoolean(EXTRA_CHILD, this.childSelected);
            outState.putBoolean(EXTRA_PARENT, this.parentSelected);
            outState.putInt(EXTRA_LEVEL, this.selectedLevel);
            //Current filter. Old text is not saved otherwise animateTo() cannot be called
            outState.putString(EXTRA_SEARCH, this.mSearchText);
            //Save headers shown status
            outState.putBoolean(EXTRA_HEADERS, this.headersShown);
        }
    }

    /**
     * Restore the previous state of the expanded items.
     *
     * @param savedInstanceState Previous state
     * @since 5.0.0-b1
     */
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        if (savedInstanceState != null) {
            //Restore headers shown status
            boolean headersShown = savedInstanceState.getBoolean(EXTRA_HEADERS);
            if (!headersShown) {
                hideAllHeaders();
            } else if (headersShown && !this.headersShown) {
                showAllHeadersWithReset(true);
            }
            this.headersShown = headersShown;
            //Restore selection state
            super.onRestoreInstanceState(savedInstanceState);
            //Restore selection coherence
            this.parentSelected = savedInstanceState.getBoolean(EXTRA_PARENT);
            this.childSelected = savedInstanceState.getBoolean(EXTRA_CHILD);
            this.selectedLevel = savedInstanceState.getInt(EXTRA_LEVEL);
            //Current filter (old text must not be saved)
            this.mSearchText = savedInstanceState.getString(EXTRA_SEARCH);
        }
    }

    /*---------------*/
    /* INNER CLASSES */
    /*---------------*/

    /**
     * @since 03/01/2016
     */
    public interface OnUpdateListener {
        /**
         * Called at startup and every time an item is inserted, removed or filtered.
         *
         * @param size the current number of items in the adapter, result of {@link #getItemCount()}
         * @since 5.0.0-b1
         */
        void onUpdateEmptyView(int size);
    }

    /**
     * @since 29/11/2015
     */
    public interface OnDeleteCompleteListener {
        /**
         * Called when Undo timeout is over and removal must be committed in the user Database.
         * <p>Due to Java Generic, it's too complicated and not
         * well manageable if we pass the List&lt;T&gt; object.<br/>
         * To get deleted items, use {@link #getDeletedItems()} from the
         * implementation of this method.</p>
         *
         * @since 5.0.0-b1
         */
        void onDeleteConfirmed();
    }

    /**
     * @since 26/01/2016
     */
    public interface OnItemClickListener {
        /**
         * Called when single tap occurs.
         * <p>Delegates the click event to the listener and checks if selection MODE if
         * SINGLE or MULTI is enabled in order to activate the ItemView.</p>
         * For Expandable Views it will toggle the Expansion if configured so.
         *
         * @param position the adapter position of the item clicked
         * @return true if the click should activate the ItemView, false for no change.
         * @since 5.0.0-b1
         */
        boolean onItemClick(int position);
    }

    /**
     * @since 26/01/2016
     */
    public interface OnItemLongClickListener {
        /**
         * Called when long tap occurs.
         * <p>This method always calls
         * {@link FlexibleViewHolder#toggleActivation}
         * after listener event is consumed in order to activate the ItemView.</p>
         * For Expandable Views it will collapse the View if configured so.
         *
         * @param position the adapter position of the item clicked
         * @since 5.0.0-b1
         */
        void onItemLongClick(int position);
    }

    /**
     * @since 06/06/2016
     */
    public interface OnActionStateListener {
        /**
         * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped
         * or when has been released.
         * <p>Override this method to receive touch events with its state.</p>
         *
         * @param viewHolder  the viewHolder touched
         * @param actionState one of {@link ItemTouchHelper#ACTION_STATE_SWIPE} or
         *                    {@link ItemTouchHelper#ACTION_STATE_DRAG} or
         *                    {@link ItemTouchHelper#ACTION_STATE_IDLE}.
         * @since 5.0.0-b7
         */
        void onActionStateChanged(RecyclerView.ViewHolder viewHolder, int actionState);
    }

    /**
     * @since 26/01/2016
     */
    public interface OnItemMoveListener extends OnActionStateListener {
        /**
         * Called when the item would like to be swapped.
         * <p>Delegate this permission to the user.</p>
         *
         * @param fromPosition the potential start position of the dragged item
         * @param toPosition   the potential resolved position of the swapped item
         * @return return true if the items can swap ({@code onItemMove()} will be called),
         * false otherwise (nothing happens)
         * @see #onItemMove(int, int)
         * @since 5.0.0-b8
         */
        boolean shouldMoveItem(int fromPosition, int toPosition);

        /**
         * Called when an item has been dragged far enough to trigger a move. <b>This is called
         * every time an item is shifted</b>, and <strong>not</strong> at the end of a "drop" event.
         * <p>The end of the "drop" event is instead handled by
         * {@link FlexibleViewHolder#onItemReleased(int)}</p>.
         *
         * @param fromPosition the start position of the moved item
         * @param toPosition   the resolved position of the moved item
         * @see #shouldMoveItem(int, int)
         * @since 5.0.0-b1
         */
        void onItemMove(int fromPosition, int toPosition);
    }

    /**
     * @since 26/01/2016
     */
    public interface OnItemSwipeListener extends OnActionStateListener {
        /**
         * Called when swiping ended its animation and Item is not visible anymore.
         *
         * @param position  the position of the item swiped
         * @param direction the direction to which the ViewHolder is swiped, one of:
         *                  {@link ItemTouchHelper#LEFT},
         *                  {@link ItemTouchHelper#RIGHT},
         *                  {@link ItemTouchHelper#UP},
         *                  {@link ItemTouchHelper#DOWN},
         * @since 5.0.0-b1
         */
        void onItemSwipe(int position, int direction);
    }

    /**
     * @since 05/03/2016
     */
    public interface OnStickyHeaderChangeListener {
        /**
         * Called when the current sticky header changed.
         *
         * @param sectionIndex the position of header, -1 if no header is sticky
         * @since 5.0.0-b1
         */
        void onStickyHeaderChange(int sectionIndex);
    }

    /**
     * @since 22/04/2016
     */
    public interface EndlessScrollListener {
        /**
         * Loads more data.
         *
         * @since 5.0.0-b6
         */
        void onLoadMore();
    }

    /**
     * Observer Class responsible to recalculate Selection and Expanded positions.
     */
    private class AdapterDataObserver extends RecyclerView.AdapterDataObserver {

        private void adjustPositions(int positionStart, int itemCount) {
            if (!filtering) {//Filtering has multiple insert and removal, we skip this process
                if (adjustSelected)//Don't, if remove range / restore
                    adjustSelected(positionStart, itemCount);
                adjustSelected = true;
            }
        }

        private void updateOrClearHeader() {
            if (mStickyHeaderHelper != null && !multiRange && !filtering) {
                mStickyHeaderHelper.updateOrClearHeader(true);
            }
        }

        /* Triggered by notifyDataSetChanged() */
        @Override
        public void onChanged() {
            updateOrClearHeader();
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            adjustPositions(positionStart, itemCount);
            updateOrClearHeader();
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            adjustPositions(positionStart, -itemCount);
            updateOrClearHeader();
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) {
            updateOrClearHeader();
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            updateOrClearHeader();
        }
    }

    private class RestoreInfo {
        // Positions
        int refPosition = -1, relativePosition = -1;
        // The item to which the deleted item is referring to
        T refItem = null, filterRefItem = null;
        // The deleted item
        T item = null;
        // Payload for the refItem
        Object payload = false;

        public RestoreInfo(T refItem, T item, Object payload) {
            this(refItem, item, -1, payload);
        }

        public RestoreInfo(T refItem, T item, int relativePosition, Object payload) {
            this.refItem = refItem;//This can be an Expandable or a Header
            this.item = item;
            this.relativePosition = relativePosition;
            this.payload = payload;
        }

        /**
         * @return the position where the deleted item should be restored
         */
        public int getRestorePosition(boolean isChild) {
            if (refPosition < 0) {
                refPosition = getGlobalPositionOf(filterRefItem != null ? filterRefItem : refItem);
            }
            T item = getItem(refPosition);
            if (isChild && isExpandable(item)) {
                //Assert the expandable children are collapsed
                recursiveCollapse(refPosition, getCurrentChildren((IExpandable) item), 0);
            } else if (isExpanded(item) && !isChild) {
                refPosition += getExpandableList((IExpandable) item).size() + 1;
            } else {
                refPosition++;
            }
            return refPosition;
        }

        public void clearFilterRef() {
            filterRefItem = null;
            refPosition = -1;
        }

        @Override
        public String toString() {
            return "RestoreInfo[item=" + item + ", refItem=" + refItem + ", filterRefItem=" + filterRefItem + "]";
        }
    }

    /**
     * Class necessary to notify the changes when using AsyncTask.
     */
    private static class Notification {

        public static final int ADD = 1, CHANGE = 2, REMOVE = 3, MOVE = 4, FULL = 0;
        int fromPosition, position, operation;

        public Notification(int position, int operation) {
            this.position = position;
            this.operation = operation;
        }

        public Notification(int fromPosition, int toPosition, int operation) {
            this(toPosition, operation);
            this.fromPosition = fromPosition;
        }
    }

    private class FilterAsyncTask extends AsyncTask<Void, Void, Void> {

        private final String TAG = FilterAsyncTask.class.getSimpleName();

        private List<T> newItems;
        private int what;

        FilterAsyncTask(int what, List<T> newItems) {
            this.what = what;
            this.newItems = newItems;
        }

        @Override
        protected void onCancelled() {
            if (DEBUG)
                Log.i(TAG, "FilterAsyncTask cancelled!");
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override
        protected Void doInBackground(Void... params) {
            switch (what) {
            case UPDATE:
                if (DEBUG)
                    Log.d(TAG, "doInBackground - started UPDATE");
                animateTo(newItems);
                if (DEBUG)
                    Log.d(TAG, "doInBackground - ended UPDATE");
                break;
            case FILTER:
                if (DEBUG)
                    Log.d(TAG, "doInBackground - started FILTER");
                filterItemsAsync(newItems);
                if (DEBUG)
                    Log.d(TAG, "doInBackground - ended FILTER");
                break;
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            //Notify all the changes
            executeNotifications();
            //Execute post data
            switch (what) {
            case UPDATE:
                postUpdate(false);
                break;
            case FILTER:
                postFilter();
                break;
            }
        }
    }

    /**
     * @param init true to skip all notifications and instant reset by calling notifyDataSetChanged
     */
    private void postUpdate(boolean init) {
        //Show headers and expanded items if Data Set not empty
        if (getItemCount() > 0) {
            expandItemsAtStartUp();
            if (headersShown) {
                showAllHeadersWithReset(init);
            }
        }
        //Execute instant reset on init
        if (init) {
            if (DEBUG)
                Log.w(TAG, "notifyDataSetChanged!");
            notifyDataSetChanged();
        }
        //Update empty view
        if (mUpdateListener != null) {
            mUpdateListener.onUpdateEmptyView(getItemCount());
        }
    }

    private void postFilter() {
        //Restore headers if necessary
        if (headersShown && !hasSearchText()) {
            showAllHeadersWithReset(false);
        }
        //Call listener to update EmptyView, assuming the filter always made a change
        if (mUpdateListener != null)
            mUpdateListener.onUpdateEmptyView(getItemCount());
    }

}