com.arlib.floatingsearchview.FloatingSearchView.java Source code

Java tutorial

Introduction

Here is the source code for com.arlib.floatingsearchview.FloatingSearchView.java

Source

/*
 * Copyright (C) 2015 Arlib
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.arlib.floatingsearchview;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.speech.RecognizerIntent;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
import android.support.v7.graphics.drawable.DrawerArrowDrawable;
import android.support.v7.view.SupportMenuInflater;
import android.support.v7.view.ViewPropertyAnimatorCompatSet;
import android.support.v7.view.menu.MenuBuilder;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.arlib.floatingsearchview.suggestions.SearchSuggestionsAdapter;
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion;
import com.arlib.floatingsearchview.util.MenuPopupHelper;
import com.arlib.floatingsearchview.util.Util;
import com.arlib.floatingsearchview.util.adapter.GestureDetectorListenerAdapter;
import com.arlib.floatingsearchview.util.adapter.OnItemTouchListenerAdapter;
import com.arlib.floatingsearchview.util.adapter.TextWatcherAdapter;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * A search UI widget that implements a floating search box also called persistent
 * search.
 */
public class FloatingSearchView extends FrameLayout {

    private static final String TAG = "FloatingSearchView";

    private final int BACKGROUND_DRAWABLE_ALPHA_SEARCH_ACTIVE = 150;

    private final int BACKGROUND_DRAWABLE_ALPHA_SEARCH_INACTIVE = 0;

    private final int MENU_ICON_ANIM_DURATION = 250;

    private final int BACKGROUND_FADE__ANIM_DURATION = 250;

    private final int ATTRS_SEARCH_BAR_MARGIN_DEFAULT = 0;

    private final boolean ATTRS_SEARCH_BAR_SHOW_MENU_ACTION_DEFAULT = true;

    private final boolean ATTRS_DISMISS_ON_OUTSIDE_TOUCH_DEFAULT = false;

    private final boolean ATTRS_SEARCH_BAR_SHOW_VOICE_ACTION_DEFAULT = false;

    private final boolean ATTRS_SEARCH_BAR_SHOW_SEARCH_KEY_DEFAULT = true;

    private final boolean ATTRS_SEARCH_BAR_SHOW_SEARCH_HINT_NOT_FOCUSED_DEFAULT = true;

    private final boolean ATTRS_HIDE_OVERFLOW_MENU_FOCUSED_DEFAULT = true;

    private final boolean ATTRS_SHOW_OVERFLOW_MENU_DEFAULT = true;

    private final int ATTRS_SUGGESTION_TEXT_SIZE_SP_DEFAULT = 18;

    private final int SUGGEST_LIST_COLLAPSE_ANIM_DURATION = 200;

    private final Interpolator SUGGEST_LIST_COLLAPSE_ANIM_INTERPOLATOR = new LinearInterpolator();

    private final int SUGGEST_ITEM_ADD_ANIM_DURATION = 250;

    private final Interpolator SUGGEST_ITEM_ADD_ANIM_INTERPOLATOR = new LinearInterpolator();

    private final int VOICE_REC_DEFAULT_REQUEST_CODE = 1024;

    private final int SUGGESTION_ITEM_ANIM_DURATION = 120;

    private final int OVERFLOW_ICON_WIDTH_DP = 36;

    private Activity mHostActivity;

    private Drawable mBackgroundDrawable;
    private boolean mDismissOnOutsideTouch = true;

    private View mQuerySection;
    private ImageView mVoiceInputOrClearButton;
    private EditText mSearchInput;
    private boolean mShowSearchKey;
    private boolean mIsFocused;
    private TextView mSearchBarTitle;
    private OnQueryChangeListener mQueryListener;
    private ProgressBar mSearchProgress;
    private ImageView mMenuSearchOrExitButton;
    private OnLeftMenuClickListener mOnMenuClickListener;
    private DrawerArrowDrawable mMenuBtnDrawable;
    private Drawable mIconClear;
    private Drawable mIconMic;
    private Drawable mIconOverflowMenu;
    private Drawable mIconBackArrow;
    private Drawable mIconSearch;
    private OnFocusChangeListener mFocusChangeListener;
    private boolean mShowMenuAction;
    private String mOldQuery = "";
    private OnSearchListener mSearchListener;
    private int mVoiceRecRequestCode = VOICE_REC_DEFAULT_REQUEST_CODE;
    private String mVoiceRecHint;
    private boolean mShowVoiceInput;
    private boolean mShowHintNotFocused;
    private String mSearchHint;
    private boolean mMenuOpen = false;
    private boolean mIsActiveOnClick;
    private ImageView mOverflowMenu;
    private MenuBuilder mMenuBuilder;
    private MenuPopupHelper mMenuPopupHelper;
    private SupportMenuInflater mMenuInflater;
    private OnMenuItemClickListener mOnOverflowMenuItemListener;
    private boolean mHideOverflowMenuFocused;
    private boolean mShowOverFlowMenu;
    private boolean mSearchEnabled;
    private boolean mSkipQueryFocusChangeEvent;
    private boolean mSkipTextChangeEvent;

    private View mDivider;

    private RelativeLayout mSuggestionsSection;
    private View mSuggestionListContainer;
    private RecyclerView mSuggestionsList;
    private SearchSuggestionsAdapter mSuggestionsAdapter;
    private boolean mIsCollapsing = false;
    private int mSuggestionsTextSizePx;

    /**
     * Interface for implementing a callback to be
     * invoked when the left menu (navigation menu) is
     * clicked.
     */
    public interface OnLeftMenuClickListener {

        /**
         * Called when the menu button was
         * clicked and the menu's state is now opened.
         */
        void onMenuOpened();

        /**
         * Called when the back button was
         * clicked and the menu's state is now closed.
         */
        void onMenuClosed();
    }

    /**
     * Interface for implementing a listener to listen
     * to when the current search has completed.
     */
    public interface OnSearchListener {

        /**
         * Called when a suggestion was clicked indicating
         * that the current search has completed.
         *
         * @param searchSuggestion
         */
        void onSuggestionClicked(SearchSuggestion searchSuggestion);

        /**
         * Called when the current search has completed
         * as a result of pressing search key in the keyboard.
         */
        void onSearchAction();
    }

    /**
     * Interface for implementing a listener to listen
     * when a menu in the overflow menu has been selected.
     */
    public interface OnMenuItemClickListener {

        /**
         * Called when a menu item in has been
         * selected.
         *
         * @param item the selected menu item.
         */
        void onMenuItemSelected(MenuItem item);
    }

    /**
     * Interface for implementing a listener to listen
     * to for state changes in the query text.
     */
    public interface OnQueryChangeListener {

        /**
         * Called when the query has changed. it will
         * be invoked when one or more characters in the
         * query was changed.
         *
         * @param oldQuery the previous query
         * @param newQuery the new query
         */
        void onSearchTextChanged(String oldQuery, String newQuery);
    }

    /**
     * Interface for implementing a listener to listen
     * to for focus state changes.
     */
    public interface OnFocusChangeListener {

        /**
         * Called when the search bar has gained focus
         * and listeners are now active.
         */
        void onFocus();

        /**
         * Called when the search bar has lost focus
         * and listeners are no more active.
         */
        void onFocusCleared();
    }

    public FloatingSearchView(Context context) {
        this(context, null);
    }

    public FloatingSearchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    private void init(AttributeSet attrs) {

        mHostActivity = getHostActivity();

        LayoutInflater layoutInflater = (LayoutInflater) getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        inflate(getContext(), R.layout.floating_search_layout, this);

        mBackgroundDrawable = new ColorDrawable(Color.BLACK);

        mQuerySection = findViewById(R.id.search_query_section);
        mVoiceInputOrClearButton = (ImageView) findViewById(R.id.search_bar_mic_or_ex);
        mSearchInput = (EditText) findViewById(R.id.search_bar_text);
        mSearchBarTitle = (TextView) findViewById(R.id.search_bar_title);
        mMenuSearchOrExitButton = (ImageView) findViewById(R.id.search_bar_exit);
        mSearchProgress = (ProgressBar) findViewById(R.id.search_bar_search_progress);
        mShowMenuAction = true;
        mOverflowMenu = (ImageView) findViewById(R.id.search_bar_overflow_menu);
        mMenuBuilder = new MenuBuilder(getContext());
        mMenuPopupHelper = new MenuPopupHelper(getContext(), mMenuBuilder, mOverflowMenu);
        initDrawables();
        mVoiceInputOrClearButton.setImageDrawable(mIconMic);
        mOverflowMenu.setImageDrawable(mIconOverflowMenu);

        mDivider = findViewById(R.id.divider);

        mSuggestionsSection = (RelativeLayout) findViewById(R.id.search_suggestions_section);
        mSuggestionListContainer = findViewById(R.id.suggestions_list_container);
        mSuggestionsList = (RecyclerView) findViewById(R.id.suggestions_list);

        setupViews(attrs);
    }

    private void initDrawables() {

        mMenuBtnDrawable = new DrawerArrowDrawable(getContext());

        mIconClear = getResources().getDrawable(R.drawable.ic_clear_black_24dp);
        mIconClear = DrawableCompat.wrap(mIconClear);

        mIconMic = getResources().getDrawable(R.drawable.ic_mic_black_24dp);
        mIconMic = DrawableCompat.wrap(mIconMic);

        mIconOverflowMenu = getResources().getDrawable(R.drawable.ic_more_vert_black_24dp);
        mIconOverflowMenu = DrawableCompat.wrap(mIconOverflowMenu);

        mIconBackArrow = getResources().getDrawable(R.drawable.ic_arrow_back_black_24dp);
        mIconBackArrow = DrawableCompat.wrap(mIconBackArrow);

        mIconSearch = getResources().getDrawable(R.drawable.ic_search_black_24dp);
        mIconSearch = DrawableCompat.wrap(mIconSearch);

        setIconsColor(getResources().getColor(R.color.gray_active_icon));
    }

    private void setIconsColor(int color) {

        mMenuBtnDrawable.setColor(color);
        DrawableCompat.setTint(mIconClear, color);
        DrawableCompat.setTint(mIconMic, color);
        DrawableCompat.setTint(mIconOverflowMenu, color);
        DrawableCompat.setTint(mIconBackArrow, color);
        DrawableCompat.setTint(mIconSearch, color);
    }

    private boolean mIsInitialLayout = true;

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        if (mIsInitialLayout) {

            int addedHeight = Util.dpToPx(5 * 3);

            //we need to add 5dp to the mSuggestionsSection because we are
            //going to move it up by 5dp in order o cover the search bar's
            //rounded corners. We also need to add an additional 10dp to
            //mSuggestionsSection in order to hide mSuggestionListContainer
            //rounded corners and top/bottom padding.
            mSuggestionsSection.getLayoutParams().height = mSuggestionsSection.getMeasuredHeight() + addedHeight;

            mIsInitialLayout = false;
        }

        //todo check if this is safe here
        adjustSearchInputPadding();

        //pass on the layout
        super.onLayout(changed, l, t, r, b);
    }

    private void setupViews(AttributeSet attrs) {

        if (attrs != null)
            applyXmlAttributes(attrs);

        mBackgroundDrawable.setAlpha(BACKGROUND_DRAWABLE_ALPHA_SEARCH_INACTIVE);

        int sdkVersion = Build.VERSION.SDK_INT;
        if (sdkVersion < Build.VERSION_CODES.JELLY_BEAN) {
            setBackgroundDrawable(mBackgroundDrawable);
        } else {
            setBackground(mBackgroundDrawable);
        }

        setupQueryBar();

        if (!isInEditMode())
            setupSuggestionSection();
    }

    private void applyXmlAttributes(AttributeSet attrs) {

        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.FloatingSearchView);

        try {

            setDismissOnOutsideClick(true);

            int searchBarWidth = a.getDimensionPixelSize(
                    R.styleable.FloatingSearchView_floatingSearch_searchBarWidth,
                    ViewGroup.LayoutParams.MATCH_PARENT);

            mQuerySection.getLayoutParams().width = searchBarWidth;
            mDivider.getLayoutParams().width = searchBarWidth;
            mSuggestionListContainer.getLayoutParams().width = searchBarWidth;

            int searchBarLeftMargin = a.getDimensionPixelSize(
                    R.styleable.FloatingSearchView_floatingSearch_searchBarMarginLeft,
                    ATTRS_SEARCH_BAR_MARGIN_DEFAULT);
            int searchBarTopMargin = a.getDimensionPixelSize(
                    R.styleable.FloatingSearchView_floatingSearch_searchBarMarginTop,
                    ATTRS_SEARCH_BAR_MARGIN_DEFAULT);
            int searchBarRightMargin = a.getDimensionPixelSize(
                    R.styleable.FloatingSearchView_floatingSearch_searchBarMarginRight,
                    ATTRS_SEARCH_BAR_MARGIN_DEFAULT);

            LayoutParams querySectionLP = (LayoutParams) mQuerySection.getLayoutParams();
            LayoutParams dividerLP = (LayoutParams) mDivider.getLayoutParams();
            LinearLayout.LayoutParams suggestListSectionLP = (LinearLayout.LayoutParams) mSuggestionsSection
                    .getLayoutParams();

            querySectionLP.setMargins(searchBarLeftMargin, searchBarTopMargin, searchBarRightMargin, 0);
            dividerLP.setMargins(searchBarLeftMargin, 0, searchBarRightMargin,
                    ((MarginLayoutParams) mDivider.getLayoutParams()).bottomMargin);
            suggestListSectionLP.setMargins(searchBarLeftMargin, 0, searchBarRightMargin, 0);

            mQuerySection.setLayoutParams(querySectionLP);
            mDivider.setLayoutParams(dividerLP);
            mSuggestionsSection.setLayoutParams(suggestListSectionLP);

            setSearchHint(a.getString(R.styleable.FloatingSearchView_floatingSearch_searchHint));

            setShowHintWhenNotFocused(
                    a.getBoolean(R.styleable.FloatingSearchView_floatingSearch_showSearchHintWhenNotFocused,
                            ATTRS_SEARCH_BAR_SHOW_SEARCH_HINT_NOT_FOCUSED_DEFAULT));

            setLeftShowMenu(a.getBoolean(R.styleable.FloatingSearchView_floatingSearch_showMenuAction,
                    ATTRS_SEARCH_BAR_SHOW_MENU_ACTION_DEFAULT));

            setShowVoiceInput(a.getBoolean(R.styleable.FloatingSearchView_floatingSearch_showVoiceInput,
                    ATTRS_SEARCH_BAR_SHOW_VOICE_ACTION_DEFAULT));

            setShowSearchKey(a.getBoolean(R.styleable.FloatingSearchView_floatingSearch_showSearchKey,
                    ATTRS_SEARCH_BAR_SHOW_SEARCH_KEY_DEFAULT));

            setVoiceSearchHint(a.getString(R.styleable.FloatingSearchView_floatingSearch_voiceRecHint));

            setDismissOnOutsideClick(
                    a.getBoolean(R.styleable.FloatingSearchView_floatingSearch_dismissOnOutsideTouch,
                            ATTRS_DISMISS_ON_OUTSIDE_TOUCH_DEFAULT));

            setShowOverflowMenu(a.getBoolean(R.styleable.FloatingSearchView_floatingSearch_showOverFlowMenu,
                    ATTRS_SHOW_OVERFLOW_MENU_DEFAULT));

            setSuggestionItemTextSize(
                    a.getDimensionPixelSize(R.styleable.FloatingSearchView_floatingSearch_searchSuggestionTextSize,
                            Util.spToPx(ATTRS_SUGGESTION_TEXT_SIZE_SP_DEFAULT)));

            if (a.hasValue(R.styleable.FloatingSearchView_floatingSearch_menu)) {
                inflateOverflowMenu(a.getResourceId(R.styleable.FloatingSearchView_floatingSearch_menu, 0));
            }

            setHideOverflowMenuWhenFocused(
                    a.getBoolean(R.styleable.FloatingSearchView_floatingSearch_hideOverflowMenuWhenFocused,
                            ATTRS_HIDE_OVERFLOW_MENU_FOCUSED_DEFAULT));

        } finally {

            a.recycle();
        }
    }

    private Activity getHostActivity() {
        Context context = getContext();
        while (context instanceof ContextWrapper) {
            if (context instanceof Activity) {
                return (Activity) context;
            }
            context = ((ContextWrapper) context).getBaseContext();
        }
        return null;
    }

    private void setupQueryBar() {

        if (!isInEditMode() && mHostActivity != null)
            mHostActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);

        mOverflowMenu.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {

                mMenuPopupHelper.show();
            }
        });

        mMenuBuilder.setCallback(new MenuBuilder.Callback() {
            @Override
            public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {

                if (mOnOverflowMenuItemListener != null)
                    mOnOverflowMenuItemListener.onMenuItemSelected(item);

                //todo check if we should care about this return or not
                return false;
            }

            @Override
            public void onMenuModeChange(MenuBuilder menu) {
            }

        });

        mVoiceInputOrClearButton.setVisibility(mShowVoiceInput ? View.VISIBLE : View.INVISIBLE);
        mVoiceInputOrClearButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {

                if (mSearchInput.getText().length() == 0) {

                    startVoiceInput();
                } else {

                    mSearchInput.setText("");
                }
            }
        });

        mSearchBarTitle.setVisibility(GONE);
        mSearchBarTitle.setTextColor(getResources().getColor(R.color.gray_active_icon));

        mSearchInput.addTextChangedListener(new TextWatcherAdapter() {

            public void onTextChanged(final CharSequence s, int start, int before, int count) {

                if (mSkipTextChangeEvent) {
                    mSkipTextChangeEvent = false;
                } else {

                    if (mSearchInput.getText().length() == 0) {

                        if (mShowVoiceInput)
                            changeIcon(mVoiceInputOrClearButton, mIconMic, true);
                        else
                            mVoiceInputOrClearButton.setVisibility(View.INVISIBLE);

                    } else if (mOldQuery.length() == 0) {
                        changeIcon(mVoiceInputOrClearButton, mIconClear, true);
                        mVoiceInputOrClearButton.setVisibility(View.VISIBLE);
                    }

                    if (mQueryListener != null && mIsFocused)
                        mQueryListener.onSearchTextChanged(mOldQuery, mSearchInput.getText().toString());

                    mOldQuery = mSearchInput.getText().toString();
                }

            }

        });

        mSearchInput.setOnFocusChangeListener(new TextView.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {

                if (mSkipQueryFocusChangeEvent) {
                    mSkipQueryFocusChangeEvent = false;
                } else {

                    if (hasFocus != mIsFocused)
                        setSearchFocused(hasFocus);
                }
            }
        });

        mSearchInput.setOnKeyListener(new OnKeyListener() {

            public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {

                if (mShowSearchKey && keyCode == KeyEvent.KEYCODE_ENTER) {

                    setSearchFocused(false);

                    if (mSearchListener != null)
                        mSearchListener.onSearchAction();

                    return true;
                }
                return false;
            }
        });

        refreshLeftIcon();

        mMenuSearchOrExitButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {

                if (mSearchInput.isFocused()) {

                    setSearchFocused(false);
                } else {

                    if (mShowMenuAction) {

                        toggleMenu();
                    } else {

                        setSearchFocused(true);
                    }
                }

            }
        });
    }

    private void refreshLeftIcon() {
        if (mShowMenuAction)
            mMenuSearchOrExitButton.setImageDrawable(mMenuBtnDrawable);
        else
            mMenuSearchOrExitButton.setImageDrawable(mIconSearch);
    }

    /**
     * Sets the menu button's color.
     *
     * @param color the color to be applied to the
     *              left menu button.
     */
    public void setLeftMenuIconColor(int color) {

        mMenuBtnDrawable.setColor(color);
    }

    /**
     * Set if search is enabled.
     *
     * <p>When enabled, the search
     * input will gain focus when clicking on it and action
     * items that are associated with search only will become
     * visible.</p>
     *
     * @param enabled True to enable search
     */
    public void setSearchEnabled(boolean enabled) {

        //todo avoid unnecessary work

        if (enabled)
            showSearchDependentActions();
        else
            hideSearchDependentActions();

        this.mSearchEnabled = enabled;
        mSearchInput.setEnabled(enabled);
    }

    private void showSearchDependentActions() {

        ViewCompat.animate(mSearchBarTitle).alpha(0.0f).setListener(new ViewPropertyAnimatorListenerAdapter() {

            @Override
            public void onAnimationStart(View view) {
                mSearchBarTitle.setVisibility(INVISIBLE);
            }

        }).start();

        ViewCompat.animate(mSearchInput).alpha(1.0f).setListener(new ViewPropertyAnimatorListenerAdapter() {

            @Override
            public void onAnimationStart(View view) {
                mSearchInput.setVisibility(VISIBLE);
            }

        }).start();

        ViewCompat.animate(mVoiceInputOrClearButton).alpha(1.0f)
                .setListener(new ViewPropertyAnimatorListenerAdapter() {

                    @Override
                    public void onAnimationEnd(View view) {
                        mVoiceInputOrClearButton.setVisibility(VISIBLE);
                    }

                }).start();
    }

    private void hideSearchDependentActions() {

        ViewCompat.animate(mSearchBarTitle).alpha(1.0f).setListener(new ViewPropertyAnimatorListenerAdapter() {

            @Override
            public void onAnimationStart(View view) {
                mSearchBarTitle.setVisibility(VISIBLE);
            }

        }).start();

        ViewCompat.animate(mSearchInput).alpha(0.0f).setListener(new ViewPropertyAnimatorListenerAdapter() {

            @Override
            public void onAnimationStart(View view) {
                mSearchInput.setVisibility(INVISIBLE);
            }

        }).start();

        ViewCompat.animate(mVoiceInputOrClearButton).alpha(0.0f)
                .setListener(new ViewPropertyAnimatorListenerAdapter() {

                    @Override
                    public void onAnimationEnd(View view) {
                        mVoiceInputOrClearButton.setVisibility(INVISIBLE);
                    }

                }).start();
    }

    /*
     * Sets the padding for the search input TextView. This
     * will set the available space for text before the TextView
     * needs to scroll to make space for the text.
     */
    private void adjustSearchInputPadding() {

        int newPaddingEnd = 0;

        newPaddingEnd += mVoiceInputOrClearButton.getWidth();

        if (mShowOverFlowMenu) {

            if (!(mHideOverflowMenuFocused && mSearchInput.isFocused()))
                newPaddingEnd += mOverflowMenu.getWidth();
        }

        mSearchInput.setPadding(0, 0, newPaddingEnd, 0);
        mSearchBarTitle.setPadding(0, 0, newPaddingEnd, 0);
    }

    /**
     * Mimics a menu click that opens the menu. Useful when for navigation
     * drawers when they open as a result of dragging.
     */
    public void openMenu(boolean withAnim) {

        openMenu(true, withAnim, false);
    }

    /**
     * Mimics a menu click that closes. Useful when fo navigation
     * drawers when they close as a result of selecting and item.
     *
     * @param withAnim true, will close the menu button with
     *                 the  Material animation
     */
    public void closeMenu(boolean withAnim) {

        closeMenu(true, withAnim, false);
    }

    /**
     * Set the visibility of the menu action
     * button.
     *
     * @param show true to make the menu button visible, false
     *             to make it invisible and place a search icon
     *             instead.
     */
    public void setLeftShowMenu(boolean show) {

        mShowMenuAction = show;
        refreshLeftIcon();
    }

    /**
     * Sets whether the overflow menu icon
     * will slide off the search bar when search
     * gains focus.
     *
     * @param hide
     */
    public void setHideOverflowMenuWhenFocused(boolean hide) {

        this.mHideOverflowMenuFocused = hide;
    }

    /**
     * Control whether the overflow menu is
     * present or not.
     *
     * @param show
     */
    public void setShowOverflowMenu(boolean show) {

        this.mShowOverFlowMenu = show;

        if (mShowVoiceInput)
            if (show)
                showOverflowMenuWithAnim(false);
            else
                hideOverflowMenu(false);
    }

    private void showOverflowMenuWithAnim(boolean withAnim) {

        mOverflowMenu.setClickable(true);

        if (withAnim) {
            ViewPropertyAnimatorCompatSet hidAnimSet = new ViewPropertyAnimatorCompatSet();
            hidAnimSet.playSequentially(ViewCompat.animate(mVoiceInputOrClearButton).translationX(0),
                    ViewCompat.animate(mOverflowMenu).alpha(1.0f)).setDuration(150).start();
        } else {

            mOverflowMenu.setAlpha(1.0f);
            mVoiceInputOrClearButton.setTranslationX(0);
        }
    }

    private void hideOverflowMenu(boolean withAnim) {

        //this is needed because we're going to move
        //mVoiceInputOrClearButton right into the position of
        //mOverflowMenu, and we don't want to receive click events
        //from mOverflowMenu.
        mOverflowMenu.setClickable(false);

        //accounts for anim direction based on the language direction
        int deltaX = isRTL() ? -Util.dpToPx(OVERFLOW_ICON_WIDTH_DP) : Util.dpToPx(OVERFLOW_ICON_WIDTH_DP);

        if (withAnim) {
            ViewPropertyAnimatorCompatSet hidAnimSet = new ViewPropertyAnimatorCompatSet();
            hidAnimSet
                    .playSequentially(ViewCompat.animate(mOverflowMenu).alpha(0.0f),
                            ViewCompat.animate(mVoiceInputOrClearButton).translationXBy(deltaX))
                    .setDuration(150).start();
        } else {

            mOverflowMenu.setAlpha(0.0f);
            mVoiceInputOrClearButton.setTranslationX(deltaX);
        }
    }

    /**
     * Inflates the menu items from
     * an xml resource.
     *
     * @param menuId a menu xml resource reference
     */
    public void inflateOverflowMenu(int menuId) {
        mMenuBuilder.clearAll();
        getMenuInflater().inflate(menuId, mMenuBuilder);
    }

    private MenuInflater getMenuInflater() {
        if (mMenuInflater == null) {
            mMenuInflater = new SupportMenuInflater(getContext());
        }
        return mMenuInflater;
    }

    private void toggleMenu() {

        if (mMenuOpen) {
            closeMenu(true, true, true);
        } else {
            openMenu(true, true, true);
        }
    }

    private void openMenu(boolean changeMenuIcon, boolean withAnim, boolean notifyListener) {

        if (mOnMenuClickListener != null && notifyListener)
            mOnMenuClickListener.onMenuOpened();

        mMenuOpen = true;
        if (changeMenuIcon)
            openMenuDrawable(mMenuBtnDrawable, withAnim);
    }

    private void closeMenu(boolean changeMenuIcon, boolean withAnim, boolean notifyListener) {

        if (mOnMenuClickListener != null && notifyListener)
            mOnMenuClickListener.onMenuClosed();

        mMenuOpen = false;
        if (changeMenuIcon)
            closeMenuDrawable(mMenuBtnDrawable, withAnim);
    }

    /**
     * Enables clients to directly manipulate
     * the menu icon's progress.
     *
     * <p>Useful for custom animation/behaviors.</p>
     *
     * @param progress the desired progress of the menu
     *                 icon's rotation. 0.0 == hamburger
     *                 shape, 1.0 == back arrow shape
     */
    public void setMenuIconProgress(float progress) {

        mMenuBtnDrawable.setProgress(progress);

        if (progress == 0)
            closeMenu(false, true, true);
        else if (progress == 1.0)
            openMenu(false);
    }

    /**
     * Set a hint that will appear in the
     * search input. Default hint is R.string.abc_search_hint
     * which is "search..." (when device language is set to english)
     *
     * @param searchHint
     */
    public void setSearchHint(String searchHint) {

        mSearchHint = searchHint != null ? searchHint : getResources().getString(R.string.abc_search_hint);

        if (mShowHintNotFocused || mSearchInput.isFocused())
            mSearchInput.setHint(mSearchHint);
        else
            mSearchInput.setHint("");
    }

    /**
     * Control whether the hint will be shown
     * when the search is not focused.
     *
     * @param show true to show hint when search
     *             is inactive
     */
    public void setShowHintWhenNotFocused(boolean show) {

        mShowHintNotFocused = show;

        if (mShowHintNotFocused)
            mSearchInput.setHint(mSearchHint);
    }

    /**
     * Sets the title for the search bar.
     *
     * <p>Note that this is the regular text, not the
     * hint. It won't have any effect if called when
     * mShowHintInactive is true.</p>
     *
     * @param title the title to be shown when search
     *              is not focused
     */
    public void setSearchBarTitle(CharSequence title) {

        mSearchBarTitle.setText(title);
    }

    /**
     * Set the visibility of the voice recognition action
     * button.
     *
     * @param show true to make the voice button visible, false
     *             to make it invisible.
     */
    public void setShowVoiceInput(boolean show) {

        mShowVoiceInput = show;
        mVoiceInputOrClearButton.setVisibility(mShowVoiceInput ? View.VISIBLE : View.GONE);
    }

    /**
     * Sets the request code with which the voice
     * recognition intent will be fired.
     *
     * <p>
     * The default request code is 1024. Clients should use
     * this methods to provide a unique request code if the
     * default code conflicts with one of their codes in order
     * to avoid undesired results.
     * </p>
     *
     * @param requestCode
     */
    public void setVoiceRecRequestCode(int requestCode) {

        mVoiceRecRequestCode = requestCode;
    }

    private void startVoiceInput() {

        Intent voiceIntent = createVoiceRecIntent(mHostActivity, mVoiceRecHint);

        if (mHostActivity != null)
            try {
                mHostActivity.startActivityForResult(voiceIntent, mVoiceRecRequestCode);
            } catch (ActivityNotFoundException e) {

            }
    }

    private Intent createVoiceRecIntent(Activity activity, String hint) {

        Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);

        intent.putExtra(RecognizerIntent.EXTRA_PROMPT, hint);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1);

        return intent;
    }

    /**
     * Sets the hit that will appear in the
     * voice recognition dialog.
     *
     * @param hint
     */
    public void setVoiceSearchHint(String hint) {
        mVoiceRecHint = hint != null ? hint : getResources().getString(R.string.abc_search_hint);
    }

    /**
     * Handles voice recognition activity return.
     *
     * <p>In order for voice rec to work, this must be called from
     * the client activity's onActivityResult()</p>
     *
     * @param requestCode  the code with which the voice recognition intent
     *                     was started.
     * @param resultCode
     * @param data
     */
    public void onHostActivityResult(int requestCode, int resultCode, Intent data) {

        if (requestCode == mVoiceRecRequestCode) {

            if (resultCode == Activity.RESULT_OK) {

                ArrayList<String> matches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
                if (!matches.isEmpty()) {

                    String newQuery = matches.get(0);

                    mSearchInput.requestFocus();

                    mSearchInput.setText(newQuery);
                    mSearchInput.setSelection(mSearchInput.getText().length());
                }
            }
        }
    }

    /**
     * Sets whether the the button with the search icon
     * will appear in the soft-keyboard or not.
     *
     * <p>Notice that if this is set to false,
     * {@link OnSearchListener#onSearchAction()} onSearchAction}, will
     * not get called.</p>
     *
     * @param show to show the search button in
     *             the soft-keyboard.
     */
    public void setShowSearchKey(boolean show) {
        mShowSearchKey = show;
        if (show)
            mSearchInput.setImeOptions(EditorInfo.IME_ACTION_SEARCH);
        else
            mSearchInput.setImeOptions(EditorInfo.IME_ACTION_NONE);
    }

    /**
     * Returns the current query text.
     *
     * @return the current query
     */
    public String getQuery() {

        return mSearchInput.getText().toString();
    }

    /**
     * Shows a circular progress on top of the
     * menu action button.
     *
     * <p>Call hidProgress()
     * to change back to normal and make the menu
     * action visible.</p>
     */
    public void showProgress() {

        mMenuSearchOrExitButton.setVisibility(View.GONE);
        mSearchProgress.setVisibility(View.VISIBLE);
        ObjectAnimator fadeInProgress = new ObjectAnimator().ofFloat(mSearchProgress, "alpha", 0.0f, 1.0f);
        fadeInProgress.start();
    }

    /**
     * Hides the progress bar after
     * a prior call to showProgress()
     */
    public void hideProgress() {

        mMenuSearchOrExitButton.setVisibility(View.VISIBLE);
        mSearchProgress.setVisibility(View.GONE);
        ObjectAnimator fadeInExit = new ObjectAnimator().ofFloat(mMenuSearchOrExitButton, "alpha", 0.0f, 1.0f);
        fadeInExit.start();
    }

    private void setSuggestionItemTextSize(int sizePx) {

        this.mSuggestionsTextSizePx = sizePx;
        //setup adapter
    }

    private void setupSuggestionSection() {

        boolean showItemsFromBottom = true;
        LinearLayoutManager layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL,
                showItemsFromBottom);
        mSuggestionsList.setLayoutManager(layoutManager);
        mSuggestionsList.setItemAnimator(null);

        final GestureDetector gestureDetector = new GestureDetector(getContext(),
                new GestureDetectorListenerAdapter() {

                    @Override
                    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {

                        if (mHostActivity != null)
                            Util.closeSoftKeyboard(mHostActivity);

                        return false;
                    }
                });

        mSuggestionsList.addOnItemTouchListener(new OnItemTouchListenerAdapter() {
            @Override
            public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
                gestureDetector.onTouchEvent(e);
                return false;
            }
        });

        mSuggestionsAdapter = new SearchSuggestionsAdapter(getContext(), mSuggestionsTextSizePx,
                new SearchSuggestionsAdapter.Listener() {

                    @Override
                    public void onItemSelected(SearchSuggestion item) {

                        setSearchFocused(false);

                        if (mSearchListener != null)
                            mSearchListener.onSuggestionClicked(item);
                    }

                    @Override
                    public void onMoveItemToSearchClicked(SearchSuggestion item) {

                        mSearchInput.setText(item.getBody());

                        //move cursor to end of text
                        mSearchInput.setSelection(mSearchInput.getText().length());
                    }
                });

        mSuggestionsList.setAdapter(mSuggestionsAdapter);

        int cardViewBottomPadding = Util.dpToPx(5);

        //move up the suggestions section enough to cover the search bar
        //card's bottom left and right corners
        mSuggestionsSection.setTranslationY(-cardViewBottomPadding);

    }

    private void moveSuggestListToInitialPos() {

        //move the suggestions list to the collapsed position
        //which is translationY of -listHeight
        mSuggestionListContainer.setTranslationY(-mSuggestionListContainer.getMeasuredHeight());
    }

    /**
     * Clears the current suggestions and replaces it
     * with the provided list of new suggestions.
     *
     * @param newSearchSuggestions a list containing the new suggestions
     */
    public void swapSuggestions(final List<? extends SearchSuggestion> newSearchSuggestions) {
        Collections.reverse(newSearchSuggestions);
        swapSuggestions(newSearchSuggestions, true);
    }

    private void swapSuggestions(final List<? extends SearchSuggestion> newSearchSuggestions, boolean withAnim) {
        mDivider.setVisibility(View.GONE);
        //update adapter
        mSuggestionsAdapter.swapData(newSearchSuggestions);

        //todo inspect line
        //this is needed because the list gets populated
        //from bottom up.
        mSuggestionsList.scrollBy(0, -(newSearchSuggestions.size() * getTotalItemsHeight(newSearchSuggestions)));

        int fiveDp = Util.dpToPx(6);
        int threeDp = Util.dpToPx(3);
        ViewCompat.animate(mSuggestionListContainer).cancel();
        float translationY = (-mSuggestionListContainer.getHeight()) + getVisibleItemsHeight(newSearchSuggestions);

        //todo refactor go over and make more clear
        final float newTranslationY = translationY < 0
                ? newSearchSuggestions.size() == 0 ? translationY : translationY + threeDp
                : -fiveDp;

        if (withAnim) {
            ViewCompat.animate(mSuggestionListContainer).setStartDelay(SUGGESTION_ITEM_ANIM_DURATION)
                    .setInterpolator(SUGGEST_ITEM_ADD_ANIM_INTERPOLATOR).setDuration(SUGGEST_ITEM_ADD_ANIM_DURATION)
                    .translationY(newTranslationY).setListener(new ViewPropertyAnimatorListenerAdapter() {

                        @Override
                        public void onAnimationCancel(View view) {

                            mSuggestionListContainer.setTranslationY(newTranslationY);
                        }
                    }).start();
        } else {

            //todo refactor
            //the extra -3*fiveDp is because this will only
            //get called from onRestoreSavedState(), and when it is called,
            //the full height of mSuggestionListContainer is not known.
            //*refactor* as soon as possible to eliminate confusion.
            float transY = translationY < 0
                    ? newSearchSuggestions.size() == 0 ? translationY : translationY + threeDp - 3 * fiveDp
                    : -fiveDp;
            mSuggestionListContainer.setTranslationY(transY);
        }

        if (newSearchSuggestions.size() > 0)
            mDivider.setVisibility(View.VISIBLE);
        else
            mDivider.setVisibility(View.GONE);
    }

    //returns the height that a given suggestion list's items
    //will take up.
    private int getVisibleItemsHeight(List<? extends SearchSuggestion> suggestions) {

        int visibleItemsHeight = 0;

        for (SearchSuggestion suggestion : suggestions) {
            visibleItemsHeight += getSuggestionItemHeight(suggestion);

            //if the current total is more than the list container's height, we
            //don't care about the rest of the items' heights because they won't be
            //visible.
            if (visibleItemsHeight > mSuggestionListContainer.getHeight())
                break;
        }

        return visibleItemsHeight;
    }

    private int getTotalItemsHeight(List<? extends SearchSuggestion> suggestions) {

        int totalItemHeight = 0;

        for (SearchSuggestion suggestion : suggestions)
            totalItemHeight += getSuggestionItemHeight(suggestion);

        return totalItemHeight;
    }

    //returns the height of a given suggestion item based on it's text length
    private int getSuggestionItemHeight(SearchSuggestion suggestion) {
        int leftRightMarginsWidth = Util.dpToPx(124);

        //todo improve efficiency
        TextView textView = new TextView(getContext());
        textView.setTypeface(Typeface.DEFAULT);
        textView.setText(suggestion.getBody(), TextView.BufferType.SPANNABLE);
        textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSuggestionsTextSizePx);
        int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(mSuggestionsList.getWidth() - leftRightMarginsWidth,
                View.MeasureSpec.AT_MOST);
        int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        textView.measure(widthMeasureSpec, heightMeasureSpec);
        int heightPlusPadding = textView.getMeasuredHeight() + Util.dpToPx(8);
        int minHeight = Util.dpToPx(48);
        int height = heightPlusPadding >= minHeight ? heightPlusPadding : minHeight;
        return heightPlusPadding >= minHeight ? heightPlusPadding : minHeight;
    }

    /**
     * Collapses the suggestions list and
     * then clears its suggestion items.
     */
    public void clearSuggestions() {
        clearSuggestions(null);
    }

    private interface OnSuggestionsClearListener {

        void onCleared();
    }

    private void clearSuggestions(final OnSuggestionsClearListener listener) {

        if (!mIsCollapsing) {

            collapseSuggestionsSection(new OnSuggestionsCollapsedListener() {
                @Override
                public void onCollapsed() {

                    mSuggestionsAdapter.clearDataSet();

                    if (listener != null)
                        listener.onCleared();

                    mDivider.setVisibility(GONE);
                }
            });
        }
    }

    private interface OnSuggestionsCollapsedListener {

        void onCollapsed();
    }

    private void collapseSuggestionsSection(final OnSuggestionsCollapsedListener listener) {

        mIsCollapsing = true;

        final int destTranslationY = -(mSuggestionListContainer.getHeight() + Util.dpToPx(3));

        ViewCompat.animate(mSuggestionListContainer).translationY(destTranslationY)
                .setDuration(SUGGEST_LIST_COLLAPSE_ANIM_DURATION)
                .setListener(new ViewPropertyAnimatorListenerAdapter() {

                    @Override
                    public void onAnimationEnd(View view) {

                        if (listener != null)
                            listener.onCollapsed();

                        mIsCollapsing = false;
                    }

                    @Override
                    public void onAnimationCancel(View view) {

                        mSuggestionListContainer.setTranslationY(destTranslationY);
                    }
                }).start();
    }

    public void clearSearchFocus() {
        setSearchFocused(false);
    }

    public boolean isSearchBarFocused() {
        return mIsFocused;
    }

    private void setSearchFocused(boolean focused) {

        this.mIsFocused = focused;

        if (focused) {

            if (mShowMenuAction && !mMenuOpen)
                openMenuDrawable(mMenuBtnDrawable, true);
            else if (!mShowMenuAction)
                changeIcon(mMenuSearchOrExitButton, mIconBackArrow, true);

            if (mMenuOpen)
                closeMenu(false, true, true);

            moveSuggestListToInitialPos();
            mSuggestionsSection.setVisibility(VISIBLE);

            fadeInBackground();

            mSearchInput.requestFocus();

            if (mShowOverFlowMenu && mHideOverflowMenuFocused)
                hideOverflowMenu(true);

            adjustSearchInputPadding();

            Util.showSoftKeyboard(getContext(), mSearchInput);

            if (mFocusChangeListener != null)
                mFocusChangeListener.onFocus();
        } else {

            if (mShowMenuAction)
                closeMenuDrawable(mMenuBtnDrawable, true);
            else
                changeIcon(mMenuSearchOrExitButton, mIconSearch, true);

            clearSuggestions(new OnSuggestionsClearListener() {
                @Override
                public void onCleared() {

                    mSuggestionsSection.setVisibility(View.INVISIBLE);
                }
            });

            fadeOutBackground();

            findViewById(R.id.search_bar).requestFocus();

            if (mHostActivity != null)
                Util.closeSoftKeyboard(mHostActivity);

            if (mShowVoiceInput && !mHideOverflowMenuFocused)
                changeIcon(mVoiceInputOrClearButton, mIconMic, true);

            if (mSearchInput.length() != 0)
                mSearchInput.setText("");

            if (mShowOverFlowMenu && mHideOverflowMenuFocused)
                showOverflowMenuWithAnim(true);

            adjustSearchInputPadding();

            if (mFocusChangeListener != null) {
                mFocusChangeListener.onFocusCleared();
            }
        }
    }

    private void changeIcon(ImageView imageView, Drawable newIcon, boolean withAnim) {

        imageView.setImageDrawable(newIcon);
        if (withAnim) {
            ObjectAnimator fadeInVoiceInputOrClear = new ObjectAnimator().ofFloat(imageView, "alpha", 0.0f, 1.0f);
            fadeInVoiceInputOrClear.start();
        } else {
            imageView.setAlpha(1.0f);
        }
    }

    /**
     * Sets the listener that will listen for query
     * changes as they are being typed.
     *
     * @param listener listener for query changes
     */
    public void setOnQueryChangeListener(OnQueryChangeListener listener) {
        this.mQueryListener = listener;
    }

    /**
     * Sets the listener that will be called when
     * an action that completes the current search
     * session has occurred and the search lost focus.
     *
     * <p>When called, a client would ideally grab the
     * search or suggestion query from the callback parameter or
     * from {@link #getQuery() getquery} and perform the necessary
     * query against its data source.</p>
     *
     * @param listener listener for query completion
     */
    public void setOnSearchListener(OnSearchListener listener) {
        this.mSearchListener = listener;
    }

    /**
     * Sets the listener that will be called when the focus
     * of the search has changed.
     *
     * @param listener listener for search focus changes
     */
    public void setOnFocusChangeListener(OnFocusChangeListener listener) {
        this.mFocusChangeListener = listener;
    }

    /**
     * Sets the listener that will be called when the
     * left/start menu (or navigation menu) is clicked.
     *
     * <p>Note that this is different from the overflow menu
     * that has a separate listener.</p>
     *
     * @param listener
     */
    public void setOnLeftMenuClickListener(OnLeftMenuClickListener listener) {
        this.mOnMenuClickListener = listener;
    }

    /**
     * Sets the listener that will be called when
     * an item in the overflow menu is clicked.
     *
     * @param listener listener to listen to menu item clicks
     */
    public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
        this.mOnOverflowMenuItemListener = listener;
    }

    private void openMenuDrawable(final DrawerArrowDrawable drawerArrowDrawable, boolean withAnim) {

        if (withAnim) {
            ValueAnimator anim = ValueAnimator.ofFloat(0.0f, 1.0f);
            anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {

                    float value = (Float) animation.getAnimatedValue();
                    drawerArrowDrawable.setProgress(value);
                }
            });
            anim.setDuration(MENU_ICON_ANIM_DURATION);
            anim.start();
        } else {
            drawerArrowDrawable.setProgress(1.0f);
        }
    }

    private void closeMenuDrawable(final DrawerArrowDrawable drawerArrowDrawable, boolean withAnim) {

        if (withAnim) {
            ValueAnimator anim = ValueAnimator.ofFloat(1.0f, 0.0f);
            anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {

                    float value = (Float) animation.getAnimatedValue();
                    drawerArrowDrawable.setProgress(value);
                }
            });
            anim.setDuration(MENU_ICON_ANIM_DURATION);
            anim.start();
        } else {
            drawerArrowDrawable.setProgress(0.0f);
        }
    }

    private void fadeOutBackground() {

        ValueAnimator anim = ValueAnimator.ofInt(BACKGROUND_DRAWABLE_ALPHA_SEARCH_ACTIVE,
                BACKGROUND_DRAWABLE_ALPHA_SEARCH_INACTIVE);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                int value = (Integer) animation.getAnimatedValue();
                mBackgroundDrawable.setAlpha(value);
            }
        });
        anim.setDuration(BACKGROUND_FADE__ANIM_DURATION);
        anim.start();
    }

    private void fadeInBackground() {

        ValueAnimator anim = ValueAnimator.ofInt(BACKGROUND_DRAWABLE_ALPHA_SEARCH_INACTIVE,
                BACKGROUND_DRAWABLE_ALPHA_SEARCH_ACTIVE);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                int value = (Integer) animation.getAnimatedValue();
                mBackgroundDrawable.setAlpha(value);
            }
        });
        anim.setDuration(BACKGROUND_FADE__ANIM_DURATION);
        anim.start();
    }

    /**
     * Set whether a touch outside of the
     * search bar's bounds will cause the search bar to
     * loos focus.
     *
     * @param enable true to dismiss on outside touch, false otherwise.
     */
    public void setDismissOnOutsideClick(boolean enable) {

        mDismissOnOutsideTouch = enable;

        mSuggestionsSection.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {

                //todo check if this is called twice
                if (mDismissOnOutsideTouch && mIsFocused)
                    setSearchFocused(false);

                return true;
            }
        });
    }

    private boolean isRTL() {

        Configuration config = getResources().getConfiguration();
        return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        return new SavedState(superState, mIsFocused, mSuggestionsAdapter.getDataSet(), getQuery());
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        final SavedState savedState = (SavedState) state;
        super.onRestoreInstanceState(savedState.getSuperState());

        if (savedState.isFocused) {

            mBackgroundDrawable.setAlpha(BACKGROUND_DRAWABLE_ALPHA_SEARCH_ACTIVE);
            mSkipTextChangeEvent = savedState.isFocused;
            mSkipQueryFocusChangeEvent = savedState.isFocused;
            mIsFocused = savedState.isFocused;

            mSuggestionsSection.setVisibility(VISIBLE);

            ViewTreeObserver vto = mSuggestionListContainer.getViewTreeObserver();
            vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    if (Build.VERSION.SDK_INT < 16) {
                        mSuggestionListContainer.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    } else {
                        mSuggestionListContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    }

                    swapSuggestions(savedState.suggestions, false);
                }
            });

            if (savedState.mQuery.length() == 0) {

                if (mShowVoiceInput)
                    changeIcon(mVoiceInputOrClearButton, mIconMic, false);
                else
                    mVoiceInputOrClearButton.setVisibility(View.INVISIBLE);

            } else if (mOldQuery.length() == 0) {
                changeIcon(mVoiceInputOrClearButton, mIconClear, false);
                mVoiceInputOrClearButton.setVisibility(View.VISIBLE);
            }

            if (mShowOverFlowMenu && mHideOverflowMenuFocused)
                hideOverflowMenu(false);

            if (mShowMenuAction && !mMenuOpen)
                openMenuDrawable(mMenuBtnDrawable, false);
            else if (!mShowMenuAction)
                changeIcon(mMenuSearchOrExitButton, mIconBackArrow, false);

            adjustSearchInputPadding();

            Util.showSoftKeyboard(getContext(), mSearchInput);
        }
    }

    static class SavedState extends BaseSavedState {

        List<? extends SearchSuggestion> suggestions = new ArrayList<>();
        Creator SUGGEST_CREATOR;

        boolean isFocused;

        String mQuery;

        SavedState(Parcelable superState, boolean isFocused, List<? extends SearchSuggestion> suggestions,
                String query) {
            super(superState);
            this.isFocused = isFocused;
            this.suggestions = suggestions;

            if (!suggestions.isEmpty())
                SUGGEST_CREATOR = suggestions.get(0).getCreator();

            this.mQuery = query;
        }

        private SavedState(Parcel in) {
            super(in);
            isFocused = (in.readInt() != 0);

            if (SUGGEST_CREATOR != null)
                in.readTypedList(suggestions, SUGGEST_CREATOR);

            mQuery = in.readString();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(isFocused ? 1 : 0);
            out.writeTypedList(suggestions);
            out.writeString(mQuery);
        }

        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        //remove any ongoing animations to prevent leaks
        ViewCompat.animate(mSuggestionListContainer).cancel();
    }
}