com.sgottard.sofa.support.BrowseSupportFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.sgottard.sofa.support.BrowseSupportFragment.java

Source

/* This file is auto-generated from BrowseFragment.java.  DO NOT MODIFY. */

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

import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v17.leanback.transition.TransitionListener;
import android.support.v17.leanback.widget.BrowseFrameLayout;
import android.support.v17.leanback.widget.HorizontalGridView;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.PresenterSelector;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowHeaderPresenter;
import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.TitleView;
import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentManager.BackStackEntry;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;

import com.sgottard.sofa.ContentFragment;
import com.sgottard.sofa.R;

import static android.support.v7.widget.RecyclerView.NO_POSITION;

/**
 * A fragment for creating Leanback browse screens. It is composed of a
 * RowsSupportFragment and a HeadersSupportFragment.
 * <p>
 * A BrowseSupportFragment renders the elements of its {@link ObjectAdapter} as a set
 * of rows in a vertical list. The elements in this adapter must be subclasses
 * of {@link Row}.
 * <p>
 * The HeadersSupportFragment can be set to be either shown or hidden by default, or
 * may be disabled entirely. See {@link #setHeadersState} for details.
 * <p>
 * By default the BrowseSupportFragment includes support for returning to the headers
 * when the user presses Back. For Activities that customize {@link
 * android.support.v4.app.FragmentActivity#onBackPressed()}, you must disable this default Back key support by
 * calling {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and
 * use {@link android.support.v17.leanback.app.BrowseSupportFragment.BrowseTransitionListener} and
 * {@link #startHeadersTransition(boolean)}.
 * <p>
 * The recommended theme to use with a BrowseSupportFragment is
 * {@link R.style#Theme_Leanback_Browse}.
 * </p>
 */
public class BrowseSupportFragment extends BaseSupportFragment {

    // BUNDLE attribute for saving header show/hide status when backstack is used:
    static final String HEADER_STACK_INDEX = "headerStackIndex";
    // BUNDLE attribute for saving header show/hide status when backstack is not used:
    static final String HEADER_SHOW = "headerShow";

    final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
        int mLastEntryCount;
        int mIndexOfHeadersBackStack;

        BackStackListener() {
            mLastEntryCount = getFragmentManager().getBackStackEntryCount();
            mIndexOfHeadersBackStack = -1;
        }

        void load(Bundle savedInstanceState) {
            if (savedInstanceState != null) {
                mIndexOfHeadersBackStack = savedInstanceState.getInt(HEADER_STACK_INDEX, -1);
                mShowingHeaders = mIndexOfHeadersBackStack == -1;
            } else {
                if (!mShowingHeaders) {
                    getFragmentManager().beginTransaction().addToBackStack(mWithHeadersBackStackName).commit();
                }
            }
        }

        void save(Bundle outState) {
            outState.putInt(HEADER_STACK_INDEX, mIndexOfHeadersBackStack);
        }

        @Override
        public void onBackStackChanged() {
            if (getFragmentManager() == null) {
                Log.w(TAG, "getFragmentManager() is null, stack:", new Exception());
                return;
            }
            int count = getFragmentManager().getBackStackEntryCount();
            // if backstack is growing and last pushed entry is "headers" backstack,
            // remember the index of the entry.
            if (count > mLastEntryCount) {
                BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1);
                if (mWithHeadersBackStackName.equals(entry.getName())) {
                    mIndexOfHeadersBackStack = count - 1;
                }
            } else if (count < mLastEntryCount) {
                // if popped "headers" backstack, initiate the show header transition if needed
                if (mIndexOfHeadersBackStack >= count) {
                    mIndexOfHeadersBackStack = -1;
                    if (!mShowingHeaders) {
                        startHeadersTransitionInternal(true);
                    }
                }
            }
            mLastEntryCount = count;
        }
    }

    /**
     * Listener for transitions between browse headers and rows.
     */
    public static class BrowseTransitionListener {
        /**
         * Callback when headers transition starts.
         *
         * @param withHeaders True if the transition will result in headers
         *        being shown, false otherwise.
         */
        public void onHeadersTransitionStart(boolean withHeaders) {
        }

        /**
         * Callback when headers transition stops.
         *
         * @param withHeaders True if the transition will result in headers
         *        being shown, false otherwise.
         */
        public void onHeadersTransitionStop(boolean withHeaders) {
        }
    }

    private class SetSelectionRunnable implements Runnable {
        static final int TYPE_INVALID = -1;
        static final int TYPE_INTERNAL_SYNC = 0;
        static final int TYPE_USER_REQUEST = 1;

        private int mPosition;
        private int mType;
        private boolean mSmooth;

        SetSelectionRunnable() {
            reset();
        }

        void post(int position, int type, boolean smooth) {
            // Posting the set selection, rather than calling it immediately, prevents an issue
            // with adapter changes.  Example: a row is added before the current selected row;
            // first the fast lane view updates its selection, then the rows fragment has that
            // new selection propagated immediately; THEN the rows view processes the same adapter
            // change and moves the selection again.
            if (type >= mType) {
                mPosition = position;
                mType = type;
                mSmooth = smooth;
                mBrowseFrame.removeCallbacks(this);
                mBrowseFrame.post(this);
            }
        }

        @Override
        public void run() {
            setSelection(mPosition, mSmooth);
            reset();
        }

        private void reset() {
            mPosition = -1;
            mType = TYPE_INVALID;
            mSmooth = false;
        }
    }

    private static final String TAG = "BrowseSupportFragment";

    private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";

    private static boolean DEBUG = false;

    /** The headers fragment is enabled and shown by default. */
    public static final int HEADERS_ENABLED = 1;

    /** The headers fragment is enabled and hidden by default. */
    public static final int HEADERS_HIDDEN = 2;

    /** The headers fragment is disabled and will never be shown. */
    public static final int HEADERS_DISABLED = 3;

    private ContentFragment mCurrentFragment;
    private RowsSupportFragment mRowsSupportFragment;
    private HeadersSupportFragment mHeadersSupportFragment;

    private ObjectAdapter mAdapter;

    private int mHeadersState = HEADERS_ENABLED;
    private int mBrandColor = Color.TRANSPARENT;
    private boolean mBrandColorSet;

    private BrowseFrameLayout mBrowseFrame;
    private boolean mHeadersBackStackEnabled = true;
    private String mWithHeadersBackStackName;
    private boolean mShowingHeaders = true;
    private boolean mCanShowHeaders = true;
    private int mContainerListMarginStart;
    private int mContainerListAlignTop;
    private boolean mRowScaleEnabled = true;
    private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
    private OnItemViewClickedListener mOnItemViewClickedListener;
    private int mSelectedPosition = -1;

    private PresenterSelector mHeaderPresenterSelector;
    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();

    // transition related:
    private Object mSceneWithHeaders;
    private Object mSceneWithoutHeaders;
    private Object mSceneAfterEntranceTransition;
    private Object mHeadersTransition;
    private BackStackListener mBackStackChangedListener;
    private BrowseTransitionListener mBrowseTransitionListener;

    private static final String ARG_TITLE = BrowseSupportFragment.class.getCanonicalName() + ".title";
    private static final String ARG_BADGE_URI = BrowseSupportFragment.class.getCanonicalName() + ".badge";
    private static final String ARG_HEADERS_STATE = BrowseSupportFragment.class.getCanonicalName()
            + ".headersState";

    /**
     * Creates arguments for a browse fragment.
     *
     * @param args The Bundle to place arguments into, or null if the method
     *        should return a new Bundle.
     * @param title The title of the BrowseSupportFragment.
     * @param headersState The initial state of the headers of the
     *        BrowseSupportFragment. Must be one of {@link #HEADERS_ENABLED}, {@link
     *        #HEADERS_HIDDEN}, or {@link #HEADERS_DISABLED}.
     * @return A Bundle with the given arguments for creating a BrowseSupportFragment.
     */
    public static Bundle createArgs(Bundle args, String title, int headersState) {
        if (args == null) {
            args = new Bundle();
        }
        args.putString(ARG_TITLE, title);
        args.putInt(ARG_HEADERS_STATE, headersState);
        return args;
    }

    /**
     * Sets the brand color for the browse fragment. The brand color is used as
     * the primary color for UI elements in the browse fragment. For example,
     * the background color of the headers fragment uses the brand color.
     *
     * @param color The color to use as the brand color of the fragment.
     */
    public void setBrandColor(int color) {
        mBrandColor = color;
        mBrandColorSet = true;

        if (mHeadersSupportFragment != null) {
            mHeadersSupportFragment.setBackgroundColor(mBrandColor);
        }
    }

    /**
     * Returns the brand color for the browse fragment.
     * The default is transparent.
     */
    public int getBrandColor() {
        return mBrandColor;
    }

    /**
     * Sets the adapter containing the rows for the fragment.
     *
     * <p>The items referenced by the adapter must be be derived from
     * {@link Row}. These rows will be used by the rows fragment and the headers
     * fragment (if not disabled) to render the browse rows.
     *
     * @param adapter An ObjectAdapter for the browse rows. All items must
     *        derive from {@link Row}.
     */
    public void setAdapter(ObjectAdapter adapter) {
        mAdapter = adapter;
        Object firstElement = mAdapter.get(0);

        if (firstElement instanceof ListRow
                && !(((ListRow) firstElement).getAdapter().get(0) instanceof RowsSupportFragment)
                && !(((ListRow) firstElement).getAdapter().get(0) instanceof ContentFragment)) {

            if (mRowsSupportFragment != null && mHeadersSupportFragment != null) {
                mHeadersSupportFragment.setAdapter(adapter);
                mRowsSupportFragment.setAdapter(adapter);
            }
        } else {
            mRowsSupportFragment = null;
            if (mHeadersSupportFragment != null) {
                mHeadersSupportFragment.setAdapter(adapter);
            }

            mCurrentFragment = (ContentFragment) ((ListRow) firstElement).getAdapter().get(0);
        }
    }

    /**
     * Returns the adapter containing the rows for the fragment.
     */
    public ObjectAdapter getAdapter() {
        return mAdapter;
    }

    /**
     * Sets an item selection listener.
     */
    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
        mExternalOnItemViewSelectedListener = listener;
    }

    /**
     * Returns an item selection listener.
     */
    public OnItemViewSelectedListener getOnItemViewSelectedListener() {
        return mExternalOnItemViewSelectedListener;
    }

    /**
     * Sets an item clicked listener on the fragment.
     * OnItemViewClickedListener will override {@link OnClickListener} that
     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
     * So in general,  developer should choose one of the listeners but not both.
     */
    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
        mOnItemViewClickedListener = listener;
        if (mRowsSupportFragment != null) {
            mRowsSupportFragment.setOnItemViewClickedListener(listener);
        }
    }

    /**
     * Returns the item Clicked listener.
     */
    public OnItemViewClickedListener getOnItemViewClickedListener() {
        return mOnItemViewClickedListener;
    }

    /**
     * Starts a headers transition.
     *
     * <p>This method will begin a transition to either show or hide the
     * headers, depending on the value of withHeaders. If headers are disabled
     * for this browse fragment, this method will throw an exception.
     *
     * @param withHeaders True if the headers should transition to being shown,
     *        false if the transition should result in headers being hidden.
     */
    public void startHeadersTransition(boolean withHeaders) {
        if (!mCanShowHeaders) {
            throw new IllegalStateException("Cannot start headers transition");
        }
        if (isInHeadersTransition() || mShowingHeaders == withHeaders) {
            return;
        }
        startHeadersTransitionInternal(withHeaders);
    }

    /**
     * Returns true if the headers transition is currently running.
     */
    public boolean isInHeadersTransition() {
        return mHeadersTransition != null;
    }

    /**
     * Returns true if headers are shown.
     */
    public boolean isShowingHeaders() {
        return mShowingHeaders;
    }

    /**
     * Sets a listener for browse fragment transitions.
     *
     * @param listener The listener to call when a browse headers transition
     *        begins or ends.
     */
    public void setBrowseTransitionListener(BrowseTransitionListener listener) {
        mBrowseTransitionListener = listener;
    }

    /**
     * Enables scaling of rows when headers are present.
     * By default enabled to increase density.
     *
     * @param enable true to enable row scaling
     */
    public void enableRowScaling(boolean enable) {
        mRowScaleEnabled = enable;
        if (mRowsSupportFragment != null) {
            mRowsSupportFragment.enableRowScaling(mRowScaleEnabled);
        }
    }

    private void startHeadersTransitionInternal(final boolean withHeaders) {
        if (getFragmentManager().isDestroyed()) {
            return;
        }
        mShowingHeaders = withHeaders;
        RowsSupportFragment target = null;
        if (mRowsSupportFragment != null) {
            target = mRowsSupportFragment;
        } else if (mCurrentFragment instanceof RowsSupportFragment) {
            target = (RowsSupportFragment) mCurrentFragment;
        }
        Runnable transitionRunnable = new Runnable() {
            @Override
            public void run() {
                mHeadersSupportFragment.onTransitionStart();
                createHeadersTransition();
                if (mBrowseTransitionListener != null) {
                    mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
                }
                sTransitionHelper.runTransition(withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders,
                        mHeadersTransition);
                if (mHeadersBackStackEnabled) {
                    if (!withHeaders) {
                        getFragmentManager().beginTransaction().addToBackStack(mWithHeadersBackStackName).commit();
                    } else {
                        int index = mBackStackChangedListener.mIndexOfHeadersBackStack;
                        if (index >= 0) {
                            BackStackEntry entry = getFragmentManager().getBackStackEntryAt(index);
                            getFragmentManager().popBackStackImmediate(entry.getId(),
                                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
                        }
                    }
                }
            }
        };
        if (target != null) {
            target.onExpandTransitionStart(!withHeaders, transitionRunnable);
        } else {
            // used for custom fragments, just run the headers transition
            transitionRunnable.run();
        }
    }

    private boolean isVerticalScrolling() {
        // don't run transition
        boolean isScrolling = (mHeadersSupportFragment.getVerticalGridView()
                .getScrollState() != HorizontalGridView.SCROLL_STATE_IDLE);
        if (mRowsSupportFragment != null) {
            isScrolling = isScrolling || mRowsSupportFragment.getVerticalGridView()
                    .getScrollState() != HorizontalGridView.SCROLL_STATE_IDLE;
        } else if (mCurrentFragment != null && mCurrentFragment instanceof ContentFragment) {
            isScrolling = isScrolling || mCurrentFragment.isScrolling();
        } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) {
            isScrolling = isScrolling || mRowsSupportFragment.getVerticalGridView()
                    .getScrollState() != HorizontalGridView.SCROLL_STATE_IDLE;
        }

        return isScrolling;
    }

    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener = new BrowseFrameLayout.OnFocusSearchListener() {
        @Override
        public View onFocusSearch(View focused, int direction) {
            // if headers is running transition,  focus stays
            if (mCanShowHeaders && isInHeadersTransition()) {
                return focused;
            }
            if (DEBUG)
                Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);

            if (getTitleView() != null && focused != getTitleView() && direction == View.FOCUS_UP) {
                return getTitleView();
            }
            if (getTitleView() != null && getTitleView().hasFocus() && direction == View.FOCUS_DOWN) {
                if (mCanShowHeaders && mShowingHeaders) {
                    return mHeadersSupportFragment.getVerticalGridView();
                } else {
                    if (mRowsSupportFragment != null) {
                        return mRowsSupportFragment.getVerticalGridView();
                    } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) {
                        return ((RowsSupportFragment) mCurrentFragment).getVerticalGridView();
                    } else {
                        return mCurrentFragment.getFocusRootView();
                    }
                }
            }

            boolean isRtl = ViewCompat.getLayoutDirection(focused) == ViewCompat.LAYOUT_DIRECTION_RTL;
            int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
            int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
            if (mCanShowHeaders && direction == towardStart) {
                if (isVerticalScrolling() || mShowingHeaders) {
                    return focused;
                }
                return mHeadersSupportFragment.getVerticalGridView();
            } else if (direction == towardEnd) {
                if (isVerticalScrolling()) {
                    return focused;
                }
                if (mRowsSupportFragment != null) {
                    return mRowsSupportFragment.getVerticalGridView();
                } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) {
                    return ((RowsSupportFragment) mCurrentFragment).getVerticalGridView();
                } else {
                    return mCurrentFragment.getFocusRootView();
                }
            } else {
                return null;
            }
        }
    };

    private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener = new BrowseFrameLayout.OnChildFocusListener() {

        @Override
        public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
            if (getChildFragmentManager().isDestroyed()) {
                return true;
            }
            // Make sure not changing focus when requestFocus() is called.
            if (mCanShowHeaders && mShowingHeaders) {
                if (mHeadersSupportFragment != null && mHeadersSupportFragment.getView() != null
                        && mHeadersSupportFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
                    return true;
                }
            }
            if (mRowsSupportFragment != null && mRowsSupportFragment.getView() != null
                    && mRowsSupportFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
                return true;
            }
            if (mCurrentFragment != null && mCurrentFragment.getFocusRootView() != null
                    && mCurrentFragment.getFocusRootView().requestFocus(direction, previouslyFocusedRect)) {
                return true;
            }
            if (getTitleView() != null && getTitleView().requestFocus(direction, previouslyFocusedRect)) {
                return true;
            }
            return false;
        };

        @Override
        public void onRequestChildFocus(View child, View focused) {
            if (getChildFragmentManager().isDestroyed()) {
                return;
            }
            if (!mCanShowHeaders || isInHeadersTransition())
                return;
            int childId = child.getId();
            if (childId == R.id.browse_container_dock && mShowingHeaders) {
                startHeadersTransitionInternal(false);
            } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
                startHeadersTransitionInternal(true);
            }
        }
    };

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mBackStackChangedListener != null) {
            mBackStackChangedListener.save(outState);
        } else {
            outState.putBoolean(HEADER_SHOW, mShowingHeaders);
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TypedArray ta = getActivity().obtainStyledAttributes(R.styleable.LeanbackTheme);
        mContainerListMarginStart = (int) ta.getDimension(R.styleable.LeanbackTheme_browseRowsMarginStart,
                getActivity().getResources().getDimensionPixelSize(R.dimen.lb_browse_rows_margin_start));
        mContainerListAlignTop = (int) ta.getDimension(R.styleable.LeanbackTheme_browseRowsMarginTop,
                getActivity().getResources().getDimensionPixelSize(R.dimen.lb_browse_rows_margin_top));
        ta.recycle();

        readArguments(getArguments());

        if (mCanShowHeaders) {
            if (mHeadersBackStackEnabled) {
                mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
                mBackStackChangedListener = new BackStackListener();
                getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
                mBackStackChangedListener.load(savedInstanceState);
            } else {
                if (savedInstanceState != null) {
                    mShowingHeaders = savedInstanceState.getBoolean(HEADER_SHOW);
                }
            }
        }
    }

    @Override
    public void onDestroy() {
        if (mBackStackChangedListener != null) {
            getFragmentManager().removeOnBackStackChangedListener(mBackStackChangedListener);
        }
        super.onDestroy();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
            mHeadersSupportFragment = new HeadersSupportFragment();
            if (mRowsSupportFragment == null && mCurrentFragment == null) {
                mRowsSupportFragment = new RowsSupportFragment();
                getChildFragmentManager().beginTransaction()
                        .replace(R.id.browse_headers_dock, mHeadersSupportFragment)
                        .replace(R.id.browse_container_dock, mRowsSupportFragment).commit();
            } else {
                getChildFragmentManager().beginTransaction()
                        .replace(R.id.browse_headers_dock, mHeadersSupportFragment)
                        .replace(R.id.browse_container_dock, (Fragment) mCurrentFragment).commit();
            }
        } else {
            mHeadersSupportFragment = (HeadersSupportFragment) getChildFragmentManager()
                    .findFragmentById(R.id.browse_headers_dock);
            Fragment fragment = getChildFragmentManager().findFragmentById(R.id.browse_container_dock);
            if (fragment instanceof RowsSupportFragment) {
                mRowsSupportFragment = (RowsSupportFragment) fragment;
            } else {
                mCurrentFragment = (ContentFragment) fragment;
            }
        }

        mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);

        if (mRowsSupportFragment != null) {
            mRowsSupportFragment.setAdapter(mAdapter);
            mRowsSupportFragment.enableRowScaling(mRowScaleEnabled);
            mRowsSupportFragment.setOnItemViewSelectedListener(mRowViewSelectedListener);
            mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
        }

        if (mHeaderPresenterSelector != null) {
            mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
        }
        mHeadersSupportFragment.setAdapter(mAdapter);
        mHeadersSupportFragment.setOnHeaderViewSelectedListener(mHeaderViewSelectedListener);
        mHeadersSupportFragment.setOnHeaderClickedListener(mHeaderClickedListener);

        View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);

        setTitleView((TitleView) root.findViewById(R.id.browse_title_group));

        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
        mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);

        if (mBrandColorSet) {
            mHeadersSupportFragment.setBackgroundColor(mBrandColor);
        }

        mSceneWithHeaders = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
            @Override
            public void run() {
                showHeaders(true);
            }
        });
        mSceneWithoutHeaders = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
            @Override
            public void run() {
                showHeaders(false);
            }
        });
        mSceneAfterEntranceTransition = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
            @Override
            public void run() {
                setEntranceTransitionEndState();
            }
        });
        return root;
    }

    private void createHeadersTransition() {
        mHeadersTransition = sTransitionHelper.loadTransition(getActivity(),
                mShowingHeaders ? R.transition.lb_browse_headers_in : R.transition.lb_browse_headers_out);

        sTransitionHelper.setTransitionListener(mHeadersTransition, new TransitionListener() {
            @Override
            public void onTransitionStart(Object transition) {
            }

            @Override
            public void onTransitionEnd(Object transition) {
                mHeadersTransition = null;
                if (mRowsSupportFragment != null) {
                    mRowsSupportFragment.onTransitionEnd();
                } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) {
                    ((RowsSupportFragment) mCurrentFragment).onTransitionEnd();
                }
                mHeadersSupportFragment.onTransitionEnd();
                if (mShowingHeaders) {
                    VerticalGridView headerGridView = mHeadersSupportFragment.getVerticalGridView();
                    if (headerGridView != null && !headerGridView.hasFocus()) {
                        headerGridView.requestFocus();
                    }
                } else {
                    VerticalGridView rowsGridView = null;
                    if (mRowsSupportFragment != null) {
                        rowsGridView = mRowsSupportFragment.getVerticalGridView();
                    } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) {
                        rowsGridView = ((RowsSupportFragment) mCurrentFragment).getVerticalGridView();
                    }
                    if (rowsGridView != null && !rowsGridView.hasFocus()) {
                        rowsGridView.requestFocus();
                    }
                }
                toggleTitle();

                if (mBrowseTransitionListener != null) {
                    mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
                }
            }
        });
    }

    /**
     * Sets the {@link PresenterSelector} used to render the row headers.
     *
     * @param headerPresenterSelector The PresenterSelector that will determine
     *        the Presenter for each row header.
     */
    public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) {
        mHeaderPresenterSelector = headerPresenterSelector;
        if (mHeadersSupportFragment != null) {
            mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
        }
    }

    private void setRowsAlignedLeft(boolean alignLeft) {
        ViewGroup.MarginLayoutParams lp;
        View containerList;
        if (mRowsSupportFragment != null) {
            containerList = mRowsSupportFragment.getView();
            lp = (ViewGroup.MarginLayoutParams) containerList.getLayoutParams();
            lp.setMarginStart(alignLeft ? 0 : mContainerListMarginStart);
            containerList.setLayoutParams(lp);
        } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) {
            containerList = ((RowsSupportFragment) mCurrentFragment).getView();
            if (containerList == null) {
                mCurrentFragment.setExtraMargin(mContainerListAlignTop, mContainerListMarginStart);
            } else {
                lp = (ViewGroup.MarginLayoutParams) containerList.getLayoutParams();
                lp.setMarginStart(alignLeft ? 0 : mContainerListMarginStart);
                containerList.setLayoutParams(lp);
            }
        } else {
            containerList = mCurrentFragment.getView();
            if (containerList == null) {
                mCurrentFragment.setExtraMargin(mContainerListAlignTop, mContainerListMarginStart);
            } else {
                lp = (ViewGroup.MarginLayoutParams) containerList.getLayoutParams();
                lp.setMarginStart(alignLeft ? 0 : mContainerListMarginStart);
                containerList.setLayoutParams(lp);
            }
        }
    }

    private void setHeadersOnScreen(boolean onScreen) {
        MarginLayoutParams lp;
        View containerList;
        containerList = mHeadersSupportFragment.getView();
        lp = (MarginLayoutParams) containerList.getLayoutParams();
        lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
        containerList.setLayoutParams(lp);
    }

    private void showHeaders(boolean show) {
        if (DEBUG)
            Log.v(TAG, "showHeaders " + show);
        mHeadersSupportFragment.setHeadersEnabled(show);
        setHeadersOnScreen(show);
        setRowsAlignedLeft(!show);
        if (mRowsSupportFragment != null) {
            mRowsSupportFragment.setExpand(!show);
        } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) {
            ((RowsSupportFragment) mCurrentFragment).setExpand(!show);
        }
    }

    private HeadersSupportFragment.OnHeaderClickedListener mHeaderClickedListener = new HeadersSupportFragment.OnHeaderClickedListener() {
        @Override
        public void onHeaderClicked() {
            if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
                return;
            }
            startHeadersTransitionInternal(false);
            if (mRowsSupportFragment != null) {
                mRowsSupportFragment.getVerticalGridView().requestFocus();
            } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) {
                ((RowsSupportFragment) mCurrentFragment).getVerticalGridView().requestFocus();
            } else {
                mCurrentFragment.getFocusRootView().requestFocus();
            }
        }
    };

    private OnItemViewSelectedListener mRowViewSelectedListener = new OnItemViewSelectedListener() {
        @Override
        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
                RowPresenter.ViewHolder rowViewHolder, Row row) {
            int position = -1;
            if (mRowsSupportFragment != null) {
                position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
                onRowSelected(position);
            } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) {
                position = ((RowsSupportFragment) mCurrentFragment).getVerticalGridView().getSelectedPosition();
                toggleTitle();
            }
            if (DEBUG)
                Log.v(TAG, "row selected position " + position);
            if (mExternalOnItemViewSelectedListener != null) {
                mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item, rowViewHolder, row);
            }
        }
    };

    private HeadersSupportFragment.OnHeaderViewSelectedListener mHeaderViewSelectedListener = new HeadersSupportFragment.OnHeaderViewSelectedListener() {
        @Override
        public void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
            int position = mHeadersSupportFragment.getVerticalGridView().getSelectedPosition();
            if (DEBUG)
                Log.v(TAG, "header selected position " + position);

            // switch fragments (if needed)
            if (mRowsSupportFragment == null) {
                ContentFragment nextFragment = (ContentFragment) ((ListRow) mAdapter.get(position)).getAdapter()
                        .get(0);
                FragmentManager cfManager = getChildFragmentManager();
                Fragment foundFragment = cfManager.findFragmentById(R.id.browse_container_dock);
                if (foundFragment == null
                        || (foundFragment instanceof ContentFragment && !foundFragment.equals(nextFragment))) {
                    FragmentTransaction transaction = cfManager.beginTransaction();
                    transaction.replace(R.id.browse_container_dock, (Fragment) nextFragment, nextFragment.getTag());
                    transaction.commit();
                    mCurrentFragment = nextFragment;
                    if (nextFragment instanceof RowsSupportFragment) {
                        ((RowsSupportFragment) nextFragment)
                                .setOnItemViewSelectedListener(mRowViewSelectedListener);
                        ((RowsSupportFragment) nextFragment)
                                .setOnItemViewClickedListener(mOnItemViewClickedListener);
                    }
                    showHeaders(mShowingHeaders);
                }
            } else {
                onRowSelected(position);
            }
        }
    };

    private void onRowSelected(int position) {
        if (position != mSelectedPosition) {
            mSetSelectionRunnable.post(position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
            toggleTitle();
        }
    }

    private void setSelection(int position, boolean smooth) {
        if (position != NO_POSITION) {
            if (mRowsSupportFragment != null) {
                mRowsSupportFragment.setSelectedPosition(position, smooth);
            }
            mHeadersSupportFragment.setSelectedPosition(position, smooth);
        }
        mSelectedPosition = position;
    }

    /**
     * Sets the selected row position with smooth animation.
     */
    public void setSelectedPosition(int position) {
        setSelectedPosition(position, true);
    }

    /**
     * Sets the selected row position.
     */
    public void setSelectedPosition(int position, boolean smooth) {
        mSetSelectionRunnable.post(position, SetSelectionRunnable.TYPE_USER_REQUEST, smooth);
    }

    @Override
    public void onStart() {
        super.onStart();
        mHeadersSupportFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
        mHeadersSupportFragment.setItemAlignment();
        if (mRowsSupportFragment != null) {
            mRowsSupportFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
            mRowsSupportFragment.setItemAlignment();
            mRowsSupportFragment.setScalePivots(0, mContainerListAlignTop);
        } else if (mCurrentFragment instanceof RowsSupportFragment) {
            ((RowsSupportFragment) mCurrentFragment).setWindowAlignmentFromTop(mContainerListAlignTop);
            ((RowsSupportFragment) mCurrentFragment).setItemAlignment();
            ((RowsSupportFragment) mCurrentFragment).setScalePivots(0, mContainerListAlignTop);
            ((RowsSupportFragment) mCurrentFragment).setOnItemViewSelectedListener(mRowViewSelectedListener);
            ((RowsSupportFragment) mCurrentFragment).setOnItemViewClickedListener(mOnItemViewClickedListener);
        } else {
            // FIXME handle custom content
        }

        if (mCanShowHeaders && mShowingHeaders && mHeadersSupportFragment.getView() != null) {
            mHeadersSupportFragment.getView().requestFocus();
        } else if (!mCanShowHeaders || !mShowingHeaders) {
            if (mRowsSupportFragment != null && mRowsSupportFragment.getView() != null) {
                mRowsSupportFragment.getView().requestFocus();
            } else if (mCurrentFragment != null) {
                mCurrentFragment.getFocusRootView().requestFocus();
            }
        }
        if (mCanShowHeaders) {
            showHeaders(mShowingHeaders);
        }
        if (isEntranceTransitionEnabled()) {
            setEntranceTransitionStartState();
        }
    }

    /**
     * Enables/disables headers transition on back key support. This is enabled by
     * default. The BrowseSupportFragment will add a back stack entry when headers are
     * showing. Running a headers transition when the back key is pressed only
     * works when the headers state is {@link #HEADERS_ENABLED} or
     * {@link #HEADERS_HIDDEN}.
     * <p>
     * NOTE: If an Activity has its own onBackPressed() handling, you must
     * disable this feature. You may use {@link #startHeadersTransition(boolean)}
     * and {@link BrowseTransitionListener} in your own back stack handling.
     */
    public final void setHeadersTransitionOnBackEnabled(boolean headersBackStackEnabled) {
        mHeadersBackStackEnabled = headersBackStackEnabled;
    }

    /**
     * Returns true if headers transition on back key support is enabled.
     */
    public final boolean isHeadersTransitionOnBackEnabled() {
        return mHeadersBackStackEnabled;
    }

    private void readArguments(Bundle args) {
        if (args == null) {
            return;
        }
        if (args.containsKey(ARG_TITLE)) {
            setTitle(args.getString(ARG_TITLE));
        }
        if (args.containsKey(ARG_HEADERS_STATE)) {
            setHeadersState(args.getInt(ARG_HEADERS_STATE));
        }
    }

    /**
     * Sets the state for the headers column in the browse fragment. Must be one
     * of {@link #HEADERS_ENABLED}, {@link #HEADERS_HIDDEN}, or
     * {@link #HEADERS_DISABLED}.
     *
     * @param headersState The state of the headers for the browse fragment.
     */
    public void setHeadersState(int headersState) {
        if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) {
            throw new IllegalArgumentException("Invalid headers state: " + headersState);
        }
        if (DEBUG)
            Log.v(TAG, "setHeadersState " + headersState);

        if (headersState != mHeadersState) {
            mHeadersState = headersState;
            switch (headersState) {
            case HEADERS_ENABLED:
                mCanShowHeaders = true;
                mShowingHeaders = true;
                break;
            case HEADERS_HIDDEN:
                mCanShowHeaders = true;
                mShowingHeaders = false;
                break;
            case HEADERS_DISABLED:
                mCanShowHeaders = false;
                mShowingHeaders = false;
                break;
            default:
                Log.w(TAG, "Unknown headers state: " + headersState);
                break;
            }
            if (mHeadersSupportFragment != null) {
                mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
            }
        }
    }

    /**
     * Returns the state of the headers column in the browse fragment.
     */
    public int getHeadersState() {
        return mHeadersState;
    }

    @Override
    protected Object createEntranceTransition() {
        return sTransitionHelper.loadTransition(getActivity(), R.transition.lb_browse_entrance_transition);
    }

    @Override
    protected void runEntranceTransition(Object entranceTransition) {
        sTransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
    }

    @Override
    protected void onEntranceTransitionStart() {
        mHeadersSupportFragment.onTransitionStart();
        if (mRowsSupportFragment != null) {
            mRowsSupportFragment.onTransitionEnd();
        } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) {
            ((RowsSupportFragment) mCurrentFragment).onTransitionStart();
        }
    }

    @Override
    protected void onEntranceTransitionEnd() {
        mRowsSupportFragment.onTransitionEnd();
        mHeadersSupportFragment.onTransitionEnd();
    }

    void setSearchOrbViewOnScreen(boolean onScreen) {
        View searchOrbView = getTitleView().getSearchAffordanceView();
        MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
        lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
        searchOrbView.setLayoutParams(lp);
    }

    void setEntranceTransitionStartState() {
        setHeadersOnScreen(false);
        setSearchOrbViewOnScreen(false);
        if (mRowsSupportFragment != null) {
            mRowsSupportFragment.setEntranceTransitionState(false);
        } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) {
            ((RowsSupportFragment) mCurrentFragment).setEntranceTransitionState(false);
        }
    }

    void setEntranceTransitionEndState() {
        setHeadersOnScreen(mShowingHeaders);
        setSearchOrbViewOnScreen(true);
        if (mRowsSupportFragment != null) {
            mRowsSupportFragment.setEntranceTransitionState(true);
        } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) {
            ((RowsSupportFragment) mCurrentFragment).setEntranceTransitionState(true);
        }
    }

    // this has been exposed to the developer, mainly to allow control over the title block
    // for custom fragments
    public void toggleTitle(boolean show) {
        showTitle(show);
    }

    private void toggleTitle() {
        int headersPosition = mHeadersSupportFragment.getVerticalGridView().getSelectedPosition();
        headersPosition = headersPosition < 0 ? 0 : headersPosition;
        int rowsPosition = 0;
        if (mRowsSupportFragment != null) {
            rowsPosition = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
        } else if (mCurrentFragment != null && mCurrentFragment instanceof RowsSupportFragment) {
            rowsPosition = ((RowsSupportFragment) mCurrentFragment).getVerticalGridView().getSelectedPosition();
        }
        if ((!mShowingHeaders && rowsPosition == 0) || (mShowingHeaders && headersPosition == 0)) {
            showTitle(true);
        } else {
            showTitle(false);
        }
    }
}