BrowseSupportFragment.java revision 489c9a861b7cde47efcd5cf6351bc9696786ae41
1/* This file is auto-generated from BrowseFragment.java.  DO NOT MODIFY. */
2
3/*
4 * Copyright (C) 2014 The Android Open Source Project
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7 * in compliance with the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software distributed under the License
12 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 * or implied. See the License for the specific language governing permissions and limitations under
14 * the License.
15 */
16package android.support.v17.leanback.app;
17
18import android.support.v4.app.Fragment;
19import android.support.v4.app.FragmentManager;
20import android.support.v4.app.FragmentManager.BackStackEntry;
21import android.support.v4.app.FragmentTransaction;
22import android.content.res.TypedArray;
23import android.graphics.Color;
24import android.graphics.Rect;
25import android.os.Bundle;
26import android.support.annotation.ColorInt;
27import android.support.v17.leanback.R;
28import android.support.v17.leanback.transition.TransitionHelper;
29import android.support.v17.leanback.transition.TransitionListener;
30import android.support.v17.leanback.widget.BrowseFrameLayout;
31import android.support.v17.leanback.widget.InvisibleRowPresenter;
32import android.support.v17.leanback.widget.ListRow;
33import android.support.v17.leanback.widget.ObjectAdapter;
34import android.support.v17.leanback.widget.OnItemViewClickedListener;
35import android.support.v17.leanback.widget.OnItemViewSelectedListener;
36import android.support.v17.leanback.widget.PageRow;
37import android.support.v17.leanback.widget.Presenter;
38import android.support.v17.leanback.widget.PresenterSelector;
39import android.support.v17.leanback.widget.Row;
40import android.support.v17.leanback.widget.RowHeaderPresenter;
41import android.support.v17.leanback.widget.RowPresenter;
42import android.support.v17.leanback.widget.ScaleFrameLayout;
43import android.support.v17.leanback.widget.TitleViewAdapter;
44import android.support.v17.leanback.widget.VerticalGridView;
45import android.support.v4.view.ViewCompat;
46import android.support.v7.widget.RecyclerView;
47import android.util.Log;
48import android.view.LayoutInflater;
49import android.view.View;
50import android.view.ViewGroup;
51import android.view.ViewGroup.MarginLayoutParams;
52import android.view.ViewTreeObserver;
53
54import java.util.HashMap;
55import java.util.Map;
56
57import static android.support.v7.widget.RecyclerView.NO_POSITION;
58
59/**
60 * A fragment for creating Leanback browse screens. It is composed of a
61 * RowsSupportFragment and a HeadersSupportFragment.
62 * <p>
63 * A BrowseSupportFragment renders the elements of its {@link ObjectAdapter} as a set
64 * of rows in a vertical list. The elements in this adapter must be subclasses
65 * of {@link Row}.
66 * <p>
67 * The HeadersSupportFragment can be set to be either shown or hidden by default, or
68 * may be disabled entirely. See {@link #setHeadersState} for details.
69 * <p>
70 * By default the BrowseSupportFragment includes support for returning to the headers
71 * when the user presses Back. For Activities that customize {@link
72 * android.support.v4.app.FragmentActivity#onBackPressed()}, you must disable this default Back key support by
73 * calling {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and
74 * use {@link BrowseSupportFragment.BrowseTransitionListener} and
75 * {@link #startHeadersTransition(boolean)}.
76 * <p>
77 * The recommended theme to use with a BrowseSupportFragment is
78 * {@link android.support.v17.leanback.R.style#Theme_Leanback_Browse}.
79 * </p>
80 */
81public class BrowseSupportFragment extends BaseSupportFragment {
82
83    // BUNDLE attribute for saving header show/hide status when backstack is used:
84    static final String HEADER_STACK_INDEX = "headerStackIndex";
85    // BUNDLE attribute for saving header show/hide status when backstack is not used:
86    static final String HEADER_SHOW = "headerShow";
87    private static final String IS_PAGE_ROW = "isPageRow";
88    private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
89
90    final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
91        int mLastEntryCount;
92        int mIndexOfHeadersBackStack;
93
94        BackStackListener() {
95            mLastEntryCount = getFragmentManager().getBackStackEntryCount();
96            mIndexOfHeadersBackStack = -1;
97        }
98
99        void load(Bundle savedInstanceState) {
100            if (savedInstanceState != null) {
101                mIndexOfHeadersBackStack = savedInstanceState.getInt(HEADER_STACK_INDEX, -1);
102                mShowingHeaders = mIndexOfHeadersBackStack == -1;
103            } else {
104                if (!mShowingHeaders) {
105                    getFragmentManager().beginTransaction()
106                            .addToBackStack(mWithHeadersBackStackName).commit();
107                }
108            }
109        }
110
111        void save(Bundle outState) {
112            outState.putInt(HEADER_STACK_INDEX, mIndexOfHeadersBackStack);
113        }
114
115
116        @Override
117        public void onBackStackChanged() {
118            if (getFragmentManager() == null) {
119                Log.w(TAG, "getFragmentManager() is null, stack:", new Exception());
120                return;
121            }
122            int count = getFragmentManager().getBackStackEntryCount();
123            // if backstack is growing and last pushed entry is "headers" backstack,
124            // remember the index of the entry.
125            if (count > mLastEntryCount) {
126                BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1);
127                if (mWithHeadersBackStackName.equals(entry.getName())) {
128                    mIndexOfHeadersBackStack = count - 1;
129                }
130            } else if (count < mLastEntryCount) {
131                // if popped "headers" backstack, initiate the show header transition if needed
132                if (mIndexOfHeadersBackStack >= count) {
133                    if (!isHeadersDataReady()) {
134                        // if main fragment was restored first before BrowseSupportFragment's adapter gets
135                        // restored: don't start header transition, but add the entry back.
136                        getFragmentManager().beginTransaction()
137                                .addToBackStack(mWithHeadersBackStackName).commit();
138                        return;
139                    }
140                    mIndexOfHeadersBackStack = -1;
141                    if (!mShowingHeaders) {
142                        startHeadersTransitionInternal(true);
143                    }
144                }
145            }
146            mLastEntryCount = count;
147        }
148    }
149
150    /**
151     * Listener for transitions between browse headers and rows.
152     */
153    public static class BrowseTransitionListener {
154        /**
155         * Callback when headers transition starts.
156         *
157         * @param withHeaders True if the transition will result in headers
158         *        being shown, false otherwise.
159         */
160        public void onHeadersTransitionStart(boolean withHeaders) {
161        }
162        /**
163         * Callback when headers transition stops.
164         *
165         * @param withHeaders True if the transition will result in headers
166         *        being shown, false otherwise.
167         */
168        public void onHeadersTransitionStop(boolean withHeaders) {
169        }
170    }
171
172    private class SetSelectionRunnable implements Runnable {
173        static final int TYPE_INVALID = -1;
174        static final int TYPE_INTERNAL_SYNC = 0;
175        static final int TYPE_USER_REQUEST = 1;
176
177        private int mPosition;
178        private int mType;
179        private boolean mSmooth;
180
181        SetSelectionRunnable() {
182            reset();
183        }
184
185        void post(int position, int type, boolean smooth) {
186            // Posting the set selection, rather than calling it immediately, prevents an issue
187            // with adapter changes.  Example: a row is added before the current selected row;
188            // first the fast lane view updates its selection, then the rows fragment has that
189            // new selection propagated immediately; THEN the rows view processes the same adapter
190            // change and moves the selection again.
191            if (type >= mType) {
192                mPosition = position;
193                mType = type;
194                mSmooth = smooth;
195                mBrowseFrame.removeCallbacks(this);
196                mBrowseFrame.post(this);
197            }
198        }
199
200        @Override
201        public void run() {
202            setSelection(mPosition, mSmooth);
203            reset();
204        }
205
206        private void reset() {
207            mPosition = -1;
208            mType = TYPE_INVALID;
209            mSmooth = false;
210        }
211    }
212
213    /**
214     * Possible set of actions that {@link BrowseSupportFragment} exposes to clients. Custom
215     * fragments can interact with {@link BrowseSupportFragment} using this interface.
216     */
217    public interface FragmentHost {
218        /**
219         * Fragments are required to invoke this callback once their view is created
220         * inside {@link Fragment#onViewCreated} method. {@link BrowseSupportFragment} starts the entrance
221         * animation only after receiving this callback. Failure to invoke this method
222         * will lead to fragment not showing up.
223         *
224         * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
225         */
226        void notifyViewCreated(MainFragmentAdapter fragmentAdapter);
227
228        /**
229         * Fragments mapped to {@link PageRow} are required to invoke this callback once their data
230         * is created for transition, the entrance animation only after receiving this callback.
231         * Failure to invoke this method will lead to fragment not showing up.
232         *
233         * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
234         */
235        void notifyDataReady(MainFragmentAdapter fragmentAdapter);
236
237        /**
238         * Show or hide title view in {@link BrowseSupportFragment} for fragments mapped to
239         * {@link PageRow}.  Otherwise the request is ignored, in that case BrowseSupportFragment is fully
240         * in control of showing/hiding title view.
241         * <p>
242         * When HeadersSupportFragment is visible, BrowseSupportFragment will hide search affordance view if
243         * there are other focusable rows above currently focused row.
244         *
245         * @param show Boolean indicating whether or not to show the title view.
246         */
247        void showTitleView(boolean show);
248    }
249
250    /**
251     * Default implementation of {@link FragmentHost} that is used only by
252     * {@link BrowseSupportFragment}.
253     */
254    private final class FragmentHostImpl implements FragmentHost {
255        boolean mShowTitleView = true;
256        boolean mDataReady = false;
257
258        @Override
259        public void notifyViewCreated(MainFragmentAdapter fragmentAdapter) {
260            performPendingStates();
261        }
262
263        @Override
264        public void notifyDataReady(MainFragmentAdapter fragmentAdapter) {
265            mDataReady = true;
266
267            // If fragment host is not the currently active fragment (in BrowseSupportFragment), then
268            // ignore the request.
269            if (mMainFragmentAdapter == null || mMainFragmentAdapter.getFragmentHost() != this) {
270                return;
271            }
272
273            // We only honor showTitle request for PageRows.
274            if (!mIsPageRow) {
275                return;
276            }
277
278            performPendingStates();
279        }
280
281        @Override
282        public void showTitleView(boolean show) {
283            mShowTitleView = show;
284
285            // If fragment host is not the currently active fragment (in BrowseSupportFragment), then
286            // ignore the request.
287            if (mMainFragmentAdapter == null || mMainFragmentAdapter.getFragmentHost() != this) {
288                return;
289            }
290
291            // We only honor showTitle request for PageRows.
292            if (!mIsPageRow) {
293                return;
294            }
295
296            updateTitleViewVisibility();
297        }
298    }
299
300    /**
301     * Interface that defines the interaction between {@link BrowseSupportFragment} and it's main
302     * content fragment. The key method is {@link MainFragmentAdapter#getFragment()},
303     * it will be used to get the fragment to be shown in the content section. Clients can
304     * provide any implementation of fragment and customize it's interaction with
305     * {@link BrowseSupportFragment} by overriding the necessary methods.
306     *
307     * <p>
308     * Clients are expected to provide
309     * an instance of {@link MainFragmentAdapterRegistry} which will be responsible for providing
310     * implementations of {@link MainFragmentAdapter} for given content types. Currently
311     * we support different types of content - {@link ListRow}, {@link PageRow} or any subtype
312     * of {@link Row}. We provide an out of the box adapter implementation for any rows other than
313     * {@link PageRow} - {@link android.support.v17.leanback.app.RowsSupportFragment.MainFragmentAdapter}.
314     *
315     * <p>
316     * {@link PageRow} is intended to give full flexibility to developers in terms of Fragment
317     * design. Users will have to provide an implementation of {@link MainFragmentAdapter}
318     * and provide that through {@link MainFragmentAdapterRegistry}.
319     * {@link MainFragmentAdapter} implementation can supply any fragment and override
320     * just those interactions that makes sense.
321     */
322    public static class MainFragmentAdapter<T extends Fragment> {
323        private boolean mScalingEnabled;
324        private final T mFragment;
325        private FragmentHostImpl mFragmentHost;
326
327        public MainFragmentAdapter(T fragment) {
328            this.mFragment = fragment;
329        }
330
331        public final T getFragment() {
332            return mFragment;
333        }
334
335        /**
336         * Returns whether its scrolling.
337         */
338        public boolean isScrolling() {
339            return false;
340        }
341
342        /**
343         * Set the visibility of titles/hovercard of browse rows.
344         */
345        public void setExpand(boolean expand) {
346        }
347
348        /**
349         * For rows that willing to participate entrance transition,  this function
350         * hide views if afterTransition is true,  show views if afterTransition is false.
351         */
352        public void setEntranceTransitionState(boolean state) {
353        }
354
355        /**
356         * Sets the window alignment and also the pivots for scale operation.
357         */
358        public void setAlignment(int windowAlignOffsetFromTop) {
359        }
360
361        /**
362         * Callback indicating transition prepare start.
363         */
364        public boolean onTransitionPrepare() {
365            return false;
366        }
367
368        /**
369         * Callback indicating transition start.
370         */
371        public void onTransitionStart() {
372        }
373
374        /**
375         * Callback indicating transition end.
376         */
377        public void onTransitionEnd() {
378        }
379
380        /**
381         * Returns whether row scaling is enabled.
382         */
383        public boolean isScalingEnabled() {
384            return mScalingEnabled;
385        }
386
387        /**
388         * Sets the row scaling property.
389         */
390        public void setScalingEnabled(boolean scalingEnabled) {
391            this.mScalingEnabled = scalingEnabled;
392        }
393
394        /**
395         * Returns the current host interface so that main fragment can interact with
396         * {@link BrowseSupportFragment}.
397         */
398        public final FragmentHost getFragmentHost() {
399            return mFragmentHost;
400        }
401
402        void setFragmentHost(FragmentHostImpl fragmentHost) {
403            this.mFragmentHost = fragmentHost;
404        }
405    }
406
407    /**
408     * Interface to be implemented by all fragments for providing an instance of
409     * {@link MainFragmentAdapter}. Both {@link RowsSupportFragment} and custom fragment provided
410     * against {@link PageRow} will need to implement this interface.
411     */
412    public interface MainFragmentAdapterProvider {
413        /**
414         * Returns an instance of {@link MainFragmentAdapter} that {@link BrowseSupportFragment}
415         * would use to communicate with the target fragment.
416         */
417        MainFragmentAdapter getMainFragmentAdapter();
418    }
419
420    /**
421     * Interface to be implemented by {@link RowsSupportFragment} and it's subclasses for providing
422     * an instance of {@link MainFragmentRowsAdapter}.
423     */
424    public interface MainFragmentRowsAdapterProvider {
425        /**
426         * Returns an instance of {@link MainFragmentRowsAdapter} that {@link BrowseSupportFragment}
427         * would use to communicate with the target fragment.
428         */
429        MainFragmentRowsAdapter getMainFragmentRowsAdapter();
430    }
431
432    /**
433     * This is used to pass information to {@link RowsSupportFragment} or its subclasses.
434     * {@link BrowseSupportFragment} uses this interface to pass row based interaction events to
435     * the target fragment.
436     */
437    public static class MainFragmentRowsAdapter<T extends Fragment> {
438        private final T mFragment;
439
440        public MainFragmentRowsAdapter(T fragment) {
441            if (fragment == null) {
442                throw new IllegalArgumentException("Fragment can't be null");
443            }
444            this.mFragment = fragment;
445        }
446
447        public final T getFragment() {
448            return mFragment;
449        }
450        /**
451         * Set the visibility titles/hover of browse rows.
452         */
453        public void setAdapter(ObjectAdapter adapter) {
454        }
455
456        /**
457         * Sets an item clicked listener on the fragment.
458         */
459        public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
460        }
461
462        /**
463         * Sets an item selection listener.
464         */
465        public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
466        }
467
468        /**
469         * Selects a Row and perform an optional task on the Row.
470         */
471        public void setSelectedPosition(int rowPosition,
472                                        boolean smooth,
473                                        final Presenter.ViewHolderTask rowHolderTask) {
474        }
475
476        /**
477         * Selects a Row.
478         */
479        public void setSelectedPosition(int rowPosition, boolean smooth) {
480        }
481
482        /**
483         * Returns the selected position.
484         */
485        public int getSelectedPosition() {
486            return 0;
487        }
488    }
489
490    private boolean createMainFragment(ObjectAdapter adapter, int position) {
491        Object item = null;
492        if (adapter == null || adapter.size() == 0) {
493            return false;
494        } else {
495            if (position < 0) {
496                position = 0;
497            } else if (position >= adapter.size()) {
498                throw new IllegalArgumentException(
499                        String.format("Invalid position %d requested", position));
500            }
501            item = adapter.get(position);
502        }
503
504        mSelectedPosition = position;
505        boolean oldIsPageRow = mIsPageRow;
506        mIsPageRow = item instanceof PageRow;
507        boolean swap;
508
509        if (mMainFragment == null) {
510            swap = true;
511        } else {
512            if (oldIsPageRow) {
513                swap = true;
514            } else {
515                swap = mIsPageRow;
516            }
517        }
518
519        if (swap) {
520            mMainFragment = mMainFragmentAdapterRegistry.createFragment(item);
521            if (!(mMainFragment instanceof MainFragmentAdapterProvider)) {
522                throw new IllegalArgumentException(
523                        "Fragment must implement MainFragmentAdapterProvider");
524            }
525
526            mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
527                    .getMainFragmentAdapter();
528            mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
529            if (!mIsPageRow) {
530                if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
531                    mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider)mMainFragment)
532                            .getMainFragmentRowsAdapter();
533                } else {
534                    mMainFragmentRowsAdapter = null;
535                }
536                mIsPageRow = mMainFragmentRowsAdapter == null;
537            } else {
538                mMainFragmentRowsAdapter = null;
539            }
540        }
541
542        return swap;
543    }
544
545    /**
546     * Factory class responsible for creating fragment given the current item. {@link ListRow}
547     * should returns {@link RowsSupportFragment} or it's subclass whereas {@link PageRow}
548     * can return any fragment class.
549     */
550    public abstract static class FragmentFactory<T extends Fragment> {
551        public abstract T createFragment(Object row);
552    }
553
554    /**
555     * FragmentFactory implementation for {@link ListRow}.
556     */
557    public static class ListRowFragmentFactory extends FragmentFactory<RowsSupportFragment> {
558        @Override
559        public RowsSupportFragment createFragment(Object row) {
560            return new RowsSupportFragment();
561        }
562    }
563
564    /**
565     * Registry class maintaining the mapping of {@link Row} subclasses to {@link FragmentFactory}.
566     * BrowseRowFragment automatically registers {@link ListRowFragmentFactory} for
567     * handling {@link ListRow}. Developers can override that and also if they want to
568     * use custom fragment, they can register a custom {@link FragmentFactory}
569     * against {@link PageRow}.
570     */
571    public final static class MainFragmentAdapterRegistry {
572        private final Map<Class, FragmentFactory> mItemToFragmentFactoryMapping = new HashMap();
573        private final static FragmentFactory sDefaultFragmentFactory = new ListRowFragmentFactory();
574
575        public MainFragmentAdapterRegistry() {
576            registerFragment(ListRow.class, sDefaultFragmentFactory);
577        }
578
579        public void registerFragment(Class rowClass, FragmentFactory factory) {
580            mItemToFragmentFactoryMapping.put(rowClass, factory);
581        }
582
583        public Fragment createFragment(Object item) {
584            if (item == null) {
585                throw new IllegalArgumentException("Item can't be null");
586            }
587
588            FragmentFactory fragmentFactory = mItemToFragmentFactoryMapping.get(item.getClass());
589            if (fragmentFactory == null && !(item instanceof PageRow)) {
590                fragmentFactory = sDefaultFragmentFactory;
591            }
592
593            return fragmentFactory.createFragment(item);
594        }
595    }
596
597    private static final String TAG = "BrowseSupportFragment";
598
599    private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
600
601    private static boolean DEBUG = false;
602
603    /** The headers fragment is enabled and shown by default. */
604    public static final int HEADERS_ENABLED = 1;
605
606    /** The headers fragment is enabled and hidden by default. */
607    public static final int HEADERS_HIDDEN = 2;
608
609    /** The headers fragment is disabled and will never be shown. */
610    public static final int HEADERS_DISABLED = 3;
611
612    private MainFragmentAdapterRegistry mMainFragmentAdapterRegistry
613            = new MainFragmentAdapterRegistry();
614    private MainFragmentAdapter mMainFragmentAdapter;
615    private Fragment mMainFragment;
616    private HeadersSupportFragment mHeadersSupportFragment;
617    private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
618
619    private ObjectAdapter mAdapter;
620    private PresenterSelector mAdapterPresenter;
621    private PresenterSelector mWrappingPresenterSelector;
622
623    private int mHeadersState = HEADERS_ENABLED;
624    private int mBrandColor = Color.TRANSPARENT;
625    private boolean mBrandColorSet;
626
627    private BrowseFrameLayout mBrowseFrame;
628    private ScaleFrameLayout mScaleFrameLayout;
629    private boolean mHeadersBackStackEnabled = true;
630    private String mWithHeadersBackStackName;
631    private boolean mShowingHeaders = true;
632    private boolean mCanShowHeaders = true;
633    private int mContainerListMarginStart;
634    private int mContainerListAlignTop;
635    private boolean mMainFragmentScaleEnabled = true;
636    private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
637    private OnItemViewClickedListener mOnItemViewClickedListener;
638    private int mSelectedPosition = -1;
639    private float mScaleFactor;
640    private boolean mIsPageRow;
641
642    private PresenterSelector mHeaderPresenterSelector;
643    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
644
645    // transition related:
646    private Object mSceneWithHeaders;
647    private Object mSceneWithoutHeaders;
648    private Object mSceneAfterEntranceTransition;
649    private Object mHeadersTransition;
650    private BackStackListener mBackStackChangedListener;
651    private BrowseTransitionListener mBrowseTransitionListener;
652
653    private static final String ARG_TITLE = BrowseSupportFragment.class.getCanonicalName() + ".title";
654    private static final String ARG_HEADERS_STATE =
655        BrowseSupportFragment.class.getCanonicalName() + ".headersState";
656
657    /**
658     * Creates arguments for a browse fragment.
659     *
660     * @param args The Bundle to place arguments into, or null if the method
661     *        should return a new Bundle.
662     * @param title The title of the BrowseSupportFragment.
663     * @param headersState The initial state of the headers of the
664     *        BrowseSupportFragment. Must be one of {@link #HEADERS_ENABLED}, {@link
665     *        #HEADERS_HIDDEN}, or {@link #HEADERS_DISABLED}.
666     * @return A Bundle with the given arguments for creating a BrowseSupportFragment.
667     */
668    public static Bundle createArgs(Bundle args, String title, int headersState) {
669        if (args == null) {
670            args = new Bundle();
671        }
672        args.putString(ARG_TITLE, title);
673        args.putInt(ARG_HEADERS_STATE, headersState);
674        return args;
675    }
676
677    /**
678     * Sets the brand color for the browse fragment. The brand color is used as
679     * the primary color for UI elements in the browse fragment. For example,
680     * the background color of the headers fragment uses the brand color.
681     *
682     * @param color The color to use as the brand color of the fragment.
683     */
684    public void setBrandColor(@ColorInt int color) {
685        mBrandColor = color;
686        mBrandColorSet = true;
687
688        if (mHeadersSupportFragment != null) {
689            mHeadersSupportFragment.setBackgroundColor(mBrandColor);
690        }
691    }
692
693    /**
694     * Returns the brand color for the browse fragment.
695     * The default is transparent.
696     */
697    @ColorInt
698    public int getBrandColor() {
699        return mBrandColor;
700    }
701
702    /**
703     * Wrapping app provided PresenterSelector to support InvisibleRowPresenter for SectionRow
704     * DividerRow and PageRow.
705     */
706    private void createAndSetWrapperPresenter() {
707        final PresenterSelector adapterPresenter = mAdapter.getPresenterSelector();
708        if (adapterPresenter == null) {
709            throw new IllegalArgumentException("Adapter.getPresenterSelector() is null");
710        }
711        if (adapterPresenter == mAdapterPresenter) {
712            return;
713        }
714        mAdapterPresenter = adapterPresenter;
715
716        Presenter[] presenters = adapterPresenter.getPresenters();
717        final Presenter invisibleRowPresenter = new InvisibleRowPresenter();
718        final Presenter[] allPresenters = new Presenter[presenters.length + 1];
719        System.arraycopy(allPresenters, 0, presenters, 0, presenters.length);
720        allPresenters[allPresenters.length - 1] = invisibleRowPresenter;
721        mAdapter.setPresenterSelector(new PresenterSelector() {
722            @Override
723            public Presenter getPresenter(Object item) {
724                Row row = (Row) item;
725                if (row.isRenderedAsRowView()) {
726                    return adapterPresenter.getPresenter(item);
727                } else {
728                    return invisibleRowPresenter;
729                }
730            }
731
732            @Override
733            public Presenter[] getPresenters() {
734                return allPresenters;
735            }
736        });
737    }
738
739    /**
740     * Sets the adapter containing the rows for the fragment.
741     *
742     * <p>The items referenced by the adapter must be be derived from
743     * {@link Row}. These rows will be used by the rows fragment and the headers
744     * fragment (if not disabled) to render the browse rows.
745     *
746     * @param adapter An ObjectAdapter for the browse rows. All items must
747     *        derive from {@link Row}.
748     */
749    public void setAdapter(ObjectAdapter adapter) {
750        mAdapter = adapter;
751        createAndSetWrapperPresenter();
752        if (getView() == null) {
753            return;
754        }
755        replaceMainFragment(mSelectedPosition);
756
757        if (adapter != null) {
758            if (mMainFragmentRowsAdapter != null) {
759                mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(adapter));
760            }
761            mHeadersSupportFragment.setAdapter(adapter);
762        }
763    }
764
765    public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
766        return mMainFragmentAdapterRegistry;
767    }
768
769    /**
770     * Returns the adapter containing the rows for the fragment.
771     */
772    public ObjectAdapter getAdapter() {
773        return mAdapter;
774    }
775
776    /**
777     * Sets an item selection listener.
778     */
779    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
780        mExternalOnItemViewSelectedListener = listener;
781    }
782
783    /**
784     * Returns an item selection listener.
785     */
786    public OnItemViewSelectedListener getOnItemViewSelectedListener() {
787        return mExternalOnItemViewSelectedListener;
788    }
789
790    /**
791     * Get RowsSupportFragment if it's bound to BrowseSupportFragment or null if either BrowseSupportFragment has
792     * not been created yet or a different fragment is bound to it.
793     *
794     * @return RowsSupportFragment if it's bound to BrowseSupportFragment or null otherwise.
795     */
796    public RowsSupportFragment getRowsSupportFragment() {
797        if (mMainFragment instanceof RowsSupportFragment) {
798            return (RowsSupportFragment) mMainFragment;
799        }
800
801        return null;
802    }
803
804    /**
805     * @return Current main fragment or null if not created.
806     */
807    public Fragment getMainFragment() {
808        return mMainFragment;
809    }
810
811    /**
812     * Get currently bound HeadersSupportFragment or null if HeadersSupportFragment has not been created yet.
813     * @return Currently bound HeadersSupportFragment or null if HeadersSupportFragment has not been created yet.
814     */
815    public HeadersSupportFragment getHeadersSupportFragment() {
816        return mHeadersSupportFragment;
817    }
818
819    /**
820     * Sets an item clicked listener on the fragment.
821     * OnItemViewClickedListener will override {@link View.OnClickListener} that
822     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
823     * So in general, developer should choose one of the listeners but not both.
824     */
825    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
826        mOnItemViewClickedListener = listener;
827        if (mMainFragmentRowsAdapter != null) {
828            mMainFragmentRowsAdapter.setOnItemViewClickedListener(listener);
829        }
830    }
831
832    /**
833     * Returns the item Clicked listener.
834     */
835    public OnItemViewClickedListener getOnItemViewClickedListener() {
836        return mOnItemViewClickedListener;
837    }
838
839    /**
840     * Starts a headers transition.
841     *
842     * <p>This method will begin a transition to either show or hide the
843     * headers, depending on the value of withHeaders. If headers are disabled
844     * for this browse fragment, this method will throw an exception.
845     *
846     * @param withHeaders True if the headers should transition to being shown,
847     *        false if the transition should result in headers being hidden.
848     */
849    public void startHeadersTransition(boolean withHeaders) {
850        if (!mCanShowHeaders) {
851            throw new IllegalStateException("Cannot start headers transition");
852        }
853        if (isInHeadersTransition() || mShowingHeaders == withHeaders) {
854            return;
855        }
856        startHeadersTransitionInternal(withHeaders);
857    }
858
859    /**
860     * Returns true if the headers transition is currently running.
861     */
862    public boolean isInHeadersTransition() {
863        return mHeadersTransition != null;
864    }
865
866    /**
867     * Returns true if headers are shown.
868     */
869    public boolean isShowingHeaders() {
870        return mShowingHeaders;
871    }
872
873    /**
874     * Sets a listener for browse fragment transitions.
875     *
876     * @param listener The listener to call when a browse headers transition
877     *        begins or ends.
878     */
879    public void setBrowseTransitionListener(BrowseTransitionListener listener) {
880        mBrowseTransitionListener = listener;
881    }
882
883    /**
884     * @deprecated use {@link BrowseSupportFragment#enableMainFragmentScaling(boolean)} instead.
885     *
886     * @param enable true to enable row scaling
887     */
888    @Deprecated
889    public void enableRowScaling(boolean enable) {
890        enableMainFragmentScaling(enable);
891    }
892
893    /**
894     * Enables scaling of main fragment when headers are present. For the page/row fragment,
895     * scaling is enabled only when both this method and
896     * {@link MainFragmentAdapter#isScalingEnabled()} are enabled.
897     *
898     * @param enable true to enable row scaling
899     */
900    public void enableMainFragmentScaling(boolean enable) {
901        mMainFragmentScaleEnabled = enable;
902    }
903
904    private void startHeadersTransitionInternal(final boolean withHeaders) {
905        if (getFragmentManager().isDestroyed()) {
906            return;
907        }
908        if (!isHeadersDataReady()) {
909            return;
910        }
911        mShowingHeaders = withHeaders;
912        mMainFragmentAdapter.onTransitionPrepare();
913        mMainFragmentAdapter.onTransitionStart();
914        onExpandTransitionStart(!withHeaders, new Runnable() {
915            @Override
916            public void run() {
917                mHeadersSupportFragment.onTransitionPrepare();
918                mHeadersSupportFragment.onTransitionStart();
919                createHeadersTransition();
920                if (mBrowseTransitionListener != null) {
921                    mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
922                }
923                TransitionHelper.runTransition(
924                        withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders, mHeadersTransition);
925                if (mHeadersBackStackEnabled) {
926                    if (!withHeaders) {
927                        getFragmentManager().beginTransaction()
928                                .addToBackStack(mWithHeadersBackStackName).commit();
929                    } else {
930                        int index = mBackStackChangedListener.mIndexOfHeadersBackStack;
931                        if (index >= 0) {
932                            BackStackEntry entry = getFragmentManager().getBackStackEntryAt(index);
933                            getFragmentManager().popBackStackImmediate(entry.getId(),
934                                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
935                        }
936                    }
937                }
938            }
939        });
940    }
941
942    boolean isVerticalScrolling() {
943        // don't run transition
944        return mHeadersSupportFragment.isScrolling() || mMainFragmentAdapter.isScrolling();
945    }
946
947
948    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
949            new BrowseFrameLayout.OnFocusSearchListener() {
950        @Override
951        public View onFocusSearch(View focused, int direction) {
952            // if headers is running transition,  focus stays
953            if (mCanShowHeaders && isInHeadersTransition()) {
954                return focused;
955            }
956            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
957
958            if (getTitleView() != null && focused != getTitleView() &&
959                    direction == View.FOCUS_UP) {
960                return getTitleView();
961            }
962            if (getTitleView() != null && getTitleView().hasFocus() &&
963                    direction == View.FOCUS_DOWN) {
964                return mCanShowHeaders && mShowingHeaders ?
965                        mHeadersSupportFragment.getVerticalGridView() : mMainFragment.getView();
966            }
967
968            boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;
969            int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
970            int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
971            if (mCanShowHeaders && direction == towardStart) {
972                if (isVerticalScrolling() || mShowingHeaders || !isHeadersDataReady()) {
973                    return focused;
974                }
975                return mHeadersSupportFragment.getVerticalGridView();
976            } else if (direction == towardEnd) {
977                if (isVerticalScrolling()) {
978                    return focused;
979                } else if (mMainFragment != null && mMainFragment.getView() != null) {
980                    return mMainFragment.getView();
981                }
982                return focused;
983            } else if (direction == View.FOCUS_DOWN && mShowingHeaders) {
984                // disable focus_down moving into PageFragment.
985                return focused;
986            } else {
987                return null;
988            }
989        }
990    };
991
992    private final boolean isHeadersDataReady() {
993        return mAdapter != null && mAdapter.size() != 0;
994    }
995
996    private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener =
997            new BrowseFrameLayout.OnChildFocusListener() {
998
999        @Override
1000        public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1001            if (getChildFragmentManager().isDestroyed()) {
1002                return true;
1003            }
1004            // Make sure not changing focus when requestFocus() is called.
1005            if (mCanShowHeaders && mShowingHeaders) {
1006                if (mHeadersSupportFragment != null && mHeadersSupportFragment.getView() != null &&
1007                        mHeadersSupportFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
1008                    return true;
1009                }
1010            }
1011            if (mMainFragment != null && mMainFragment.getView() != null &&
1012                    mMainFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
1013                return true;
1014            }
1015            if (getTitleView() != null &&
1016                    getTitleView().requestFocus(direction, previouslyFocusedRect)) {
1017                return true;
1018            }
1019            return false;
1020        }
1021
1022        @Override
1023        public void onRequestChildFocus(View child, View focused) {
1024            if (getChildFragmentManager().isDestroyed()) {
1025                return;
1026            }
1027            if (!mCanShowHeaders || isInHeadersTransition()) return;
1028            int childId = child.getId();
1029            if (childId == R.id.browse_container_dock && mShowingHeaders) {
1030                startHeadersTransitionInternal(false);
1031            } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
1032                startHeadersTransitionInternal(true);
1033            }
1034        }
1035    };
1036
1037    @Override
1038    public void onSaveInstanceState(Bundle outState) {
1039        super.onSaveInstanceState(outState);
1040        outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
1041        outState.putBoolean(IS_PAGE_ROW, mIsPageRow);
1042
1043        if (mBackStackChangedListener != null) {
1044            mBackStackChangedListener.save(outState);
1045        } else {
1046            outState.putBoolean(HEADER_SHOW, mShowingHeaders);
1047        }
1048    }
1049
1050    @Override
1051    public void onCreate(Bundle savedInstanceState) {
1052        super.onCreate(savedInstanceState);
1053        TypedArray ta = getActivity().obtainStyledAttributes(R.styleable.LeanbackTheme);
1054        mContainerListMarginStart = (int) ta.getDimension(
1055                R.styleable.LeanbackTheme_browseRowsMarginStart, getActivity().getResources()
1056                .getDimensionPixelSize(R.dimen.lb_browse_rows_margin_start));
1057        mContainerListAlignTop = (int) ta.getDimension(
1058                R.styleable.LeanbackTheme_browseRowsMarginTop, getActivity().getResources()
1059                .getDimensionPixelSize(R.dimen.lb_browse_rows_margin_top));
1060        ta.recycle();
1061
1062        readArguments(getArguments());
1063
1064        if (mCanShowHeaders) {
1065            if (mHeadersBackStackEnabled) {
1066                mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
1067                mBackStackChangedListener = new BackStackListener();
1068                getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
1069                mBackStackChangedListener.load(savedInstanceState);
1070            } else {
1071                if (savedInstanceState != null) {
1072                    mShowingHeaders = savedInstanceState.getBoolean(HEADER_SHOW);
1073                }
1074            }
1075        }
1076
1077        mScaleFactor = getResources().getFraction(R.fraction.lb_browse_rows_scale, 1, 1);
1078    }
1079
1080    @Override
1081    public void onDestroyView() {
1082        mMainFragmentRowsAdapter = null;
1083        mMainFragmentAdapter = null;
1084        mMainFragment = null;
1085        mHeadersSupportFragment = null;
1086        super.onDestroyView();
1087    }
1088
1089    @Override
1090    public void onDestroy() {
1091        if (mBackStackChangedListener != null) {
1092            getFragmentManager().removeOnBackStackChangedListener(mBackStackChangedListener);
1093        }
1094        super.onDestroy();
1095    }
1096
1097    @Override
1098    public View onCreateView(LayoutInflater inflater, ViewGroup container,
1099            Bundle savedInstanceState) {
1100
1101        if (getChildFragmentManager().findFragmentById(R.id.scale_frame) == null) {
1102            mHeadersSupportFragment = new HeadersSupportFragment();
1103
1104            createMainFragment(mAdapter, mSelectedPosition);
1105            FragmentTransaction ft = getChildFragmentManager().beginTransaction()
1106                    .replace(R.id.browse_headers_dock, mHeadersSupportFragment);
1107
1108            if (mMainFragment != null) {
1109                ft.replace(R.id.scale_frame, mMainFragment);
1110            } else {
1111                // Empty adapter used to guard against lazy adapter loading. When this
1112                // fragment is instantiated, mAdapter might not have the data or might not
1113                // have been set. In either of those cases mFragmentAdapter will be null.
1114                // This way we can maintain the invariant that mMainFragmentAdapter is never
1115                // null and it avoids doing null checks all over the code.
1116                mMainFragmentAdapter = new MainFragmentAdapter(null);
1117                mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
1118            }
1119
1120            ft.commit();
1121        } else {
1122            mHeadersSupportFragment = (HeadersSupportFragment) getChildFragmentManager()
1123                    .findFragmentById(R.id.browse_headers_dock);
1124            mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
1125            mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
1126                    .getMainFragmentAdapter();
1127            mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
1128
1129            mIsPageRow = savedInstanceState != null ?
1130                    savedInstanceState.getBoolean(IS_PAGE_ROW, false) : false;
1131
1132            mSelectedPosition = savedInstanceState != null ?
1133                    savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0;
1134
1135            if (!mIsPageRow) {
1136                if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
1137                    mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider) mMainFragment)
1138                            .getMainFragmentRowsAdapter();
1139                } else {
1140                    mMainFragmentRowsAdapter = null;
1141                }
1142            } else {
1143                mMainFragmentRowsAdapter = null;
1144            }
1145        }
1146
1147        mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
1148        if (mHeaderPresenterSelector != null) {
1149            mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
1150        }
1151        mHeadersSupportFragment.setAdapter(mAdapter);
1152        mHeadersSupportFragment.setOnHeaderViewSelectedListener(mHeaderViewSelectedListener);
1153        mHeadersSupportFragment.setOnHeaderClickedListener(mHeaderClickedListener);
1154
1155        View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
1156
1157        getProgressBarManager().setRootView((ViewGroup)root);
1158
1159        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
1160        mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
1161        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
1162
1163        installTitleView(inflater, mBrowseFrame, savedInstanceState);
1164
1165        mScaleFrameLayout = (ScaleFrameLayout) root.findViewById(R.id.scale_frame);
1166        mScaleFrameLayout.setPivotX(0);
1167        mScaleFrameLayout.setPivotY(mContainerListAlignTop);
1168
1169        setupMainFragment();
1170
1171        if (mBrandColorSet) {
1172            mHeadersSupportFragment.setBackgroundColor(mBrandColor);
1173        }
1174
1175        mSceneWithHeaders = TransitionHelper.createScene(mBrowseFrame, new Runnable() {
1176            @Override
1177            public void run() {
1178                showHeaders(true);
1179            }
1180        });
1181        mSceneWithoutHeaders =  TransitionHelper.createScene(mBrowseFrame, new Runnable() {
1182            @Override
1183            public void run() {
1184                showHeaders(false);
1185            }
1186        });
1187        mSceneAfterEntranceTransition = TransitionHelper.createScene(mBrowseFrame, new Runnable() {
1188            @Override
1189            public void run() {
1190                setEntranceTransitionEndState();
1191            }
1192        });
1193
1194        return root;
1195    }
1196
1197    private void setupMainFragment() {
1198        if (mMainFragmentRowsAdapter != null) {
1199            mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(mAdapter));
1200            mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
1201                    new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
1202            mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
1203        }
1204    }
1205
1206    @Override
1207    boolean isReadyForPrepareEntranceTransition() {
1208        return mMainFragment != null && mMainFragment.getView() != null;
1209    }
1210
1211    @Override
1212    boolean isReadyForStartEntranceTransition() {
1213        return mMainFragment != null && mMainFragment.getView() != null
1214                && (!mIsPageRow || mMainFragmentAdapter.mFragmentHost.mDataReady);
1215    }
1216
1217    private void createHeadersTransition() {
1218        mHeadersTransition = TransitionHelper.loadTransition(getActivity(),
1219                mShowingHeaders ?
1220                R.transition.lb_browse_headers_in : R.transition.lb_browse_headers_out);
1221
1222        TransitionHelper.addTransitionListener(mHeadersTransition, new TransitionListener() {
1223            @Override
1224            public void onTransitionStart(Object transition) {
1225            }
1226            @Override
1227            public void onTransitionEnd(Object transition) {
1228                mHeadersTransition = null;
1229                if (mMainFragmentAdapter != null) {
1230                    mMainFragmentAdapter.onTransitionEnd();
1231                    if (!mShowingHeaders && mMainFragment != null) {
1232                        View mainFragmentView = mMainFragment.getView();
1233                        if (mainFragmentView != null && !mainFragmentView.hasFocus()) {
1234                            mainFragmentView.requestFocus();
1235                        }
1236                    }
1237                }
1238                if (mHeadersSupportFragment != null) {
1239                    mHeadersSupportFragment.onTransitionEnd();
1240                    if (mShowingHeaders) {
1241                        VerticalGridView headerGridView = mHeadersSupportFragment.getVerticalGridView();
1242                        if (headerGridView != null && !headerGridView.hasFocus()) {
1243                            headerGridView.requestFocus();
1244                        }
1245                    }
1246                }
1247
1248                // Animate TitleView once header animation is complete.
1249                updateTitleViewVisibility();
1250
1251                if (mBrowseTransitionListener != null) {
1252                    mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
1253                }
1254            }
1255        });
1256    }
1257
1258    void updateTitleViewVisibility() {
1259        if (!mShowingHeaders) {
1260            boolean showTitleView;
1261            if (mIsPageRow && mMainFragmentAdapter != null) {
1262                // page fragment case:
1263                showTitleView = mMainFragmentAdapter.mFragmentHost.mShowTitleView;
1264            } else {
1265                // regular row view case:
1266                showTitleView = isFirstRowWithContent(mSelectedPosition);
1267            }
1268            if (showTitleView) {
1269                showTitle(TitleViewAdapter.FULL_VIEW_VISIBLE);
1270            } else {
1271                showTitle(false);
1272            }
1273        } else {
1274            // when HeaderFragment is showing,  showBranding and showSearch are slightly different
1275            boolean showBranding;
1276            boolean showSearch;
1277            if (mIsPageRow && mMainFragmentAdapter != null) {
1278                showBranding = mMainFragmentAdapter.mFragmentHost.mShowTitleView;
1279            } else {
1280                showBranding = isFirstRowWithContent(mSelectedPosition);
1281            }
1282            showSearch = isFirstRowWithContentOrPageRow(mSelectedPosition);
1283            int flags = 0;
1284            if (showBranding) flags |= TitleViewAdapter.BRANDING_VIEW_VISIBLE;
1285            if (showSearch) flags |= TitleViewAdapter.SEARCH_VIEW_VISIBLE;
1286            if (flags != 0) {
1287                showTitle(flags);
1288            } else {
1289                showTitle(false);
1290            }
1291        }
1292    }
1293
1294    boolean isFirstRowWithContentOrPageRow(int rowPosition) {
1295        if (mAdapter == null || mAdapter.size() == 0) {
1296            return true;
1297        }
1298        for (int i = 0; i < mAdapter.size(); i++) {
1299            final Row row = (Row) mAdapter.get(i);
1300            if (row.isRenderedAsRowView() || row instanceof PageRow) {
1301                return rowPosition == i;
1302            }
1303        }
1304        return true;
1305    }
1306
1307    boolean isFirstRowWithContent(int rowPosition) {
1308        if (mAdapter == null || mAdapter.size() == 0) {
1309            return true;
1310        }
1311        for (int i = 0; i < mAdapter.size(); i++) {
1312            final Row row = (Row) mAdapter.get(i);
1313            if (row.isRenderedAsRowView()) {
1314                return rowPosition == i;
1315            }
1316        }
1317        return true;
1318    }
1319
1320    /**
1321     * Sets the {@link PresenterSelector} used to render the row headers.
1322     *
1323     * @param headerPresenterSelector The PresenterSelector that will determine
1324     *        the Presenter for each row header.
1325     */
1326    public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) {
1327        mHeaderPresenterSelector = headerPresenterSelector;
1328        if (mHeadersSupportFragment != null) {
1329            mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
1330        }
1331    }
1332
1333    private void setHeadersOnScreen(boolean onScreen) {
1334        MarginLayoutParams lp;
1335        View containerList;
1336        containerList = mHeadersSupportFragment.getView();
1337        lp = (MarginLayoutParams) containerList.getLayoutParams();
1338        lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
1339        containerList.setLayoutParams(lp);
1340    }
1341
1342    private void showHeaders(boolean show) {
1343        if (DEBUG) Log.v(TAG, "showHeaders " + show);
1344        mHeadersSupportFragment.setHeadersEnabled(show);
1345        setHeadersOnScreen(show);
1346        expandMainFragment(!show);
1347    }
1348
1349    private void expandMainFragment(boolean expand) {
1350        MarginLayoutParams params = (MarginLayoutParams) mScaleFrameLayout.getLayoutParams();
1351        params.setMarginStart(!expand ? mContainerListMarginStart : 0);
1352        mScaleFrameLayout.setLayoutParams(params);
1353        mMainFragmentAdapter.setExpand(expand);
1354
1355        setMainFragmentAlignment();
1356        final float scaleFactor = !expand
1357                && mMainFragmentScaleEnabled
1358                && mMainFragmentAdapter.isScalingEnabled() ? mScaleFactor : 1;
1359        mScaleFrameLayout.setLayoutScaleY(scaleFactor);
1360        mScaleFrameLayout.setChildScale(scaleFactor);
1361    }
1362
1363    private HeadersSupportFragment.OnHeaderClickedListener mHeaderClickedListener =
1364        new HeadersSupportFragment.OnHeaderClickedListener() {
1365            @Override
1366            public void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
1367                if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
1368                    return;
1369                }
1370                startHeadersTransitionInternal(false);
1371                mMainFragment.getView().requestFocus();
1372            }
1373        };
1374
1375    class MainFragmentItemViewSelectedListener implements OnItemViewSelectedListener {
1376        MainFragmentRowsAdapter mMainFragmentRowsAdapter;
1377
1378        public MainFragmentItemViewSelectedListener(MainFragmentRowsAdapter fragmentRowsAdapter) {
1379            mMainFragmentRowsAdapter = fragmentRowsAdapter;
1380        }
1381
1382        @Override
1383        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
1384                RowPresenter.ViewHolder rowViewHolder, Row row) {
1385            int position = mMainFragmentRowsAdapter.getSelectedPosition();
1386            if (DEBUG) Log.v(TAG, "row selected position " + position);
1387            onRowSelected(position);
1388            if (mExternalOnItemViewSelectedListener != null) {
1389                mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
1390                        rowViewHolder, row);
1391            }
1392        }
1393    };
1394
1395    private HeadersSupportFragment.OnHeaderViewSelectedListener mHeaderViewSelectedListener =
1396            new HeadersSupportFragment.OnHeaderViewSelectedListener() {
1397        @Override
1398        public void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
1399            int position = mHeadersSupportFragment.getSelectedPosition();
1400            if (DEBUG) Log.v(TAG, "header selected position " + position);
1401            onRowSelected(position);
1402        }
1403    };
1404
1405    private void onRowSelected(int position) {
1406        if (position != mSelectedPosition) {
1407            mSetSelectionRunnable.post(
1408                    position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
1409        }
1410    }
1411
1412    private void setSelection(int position, boolean smooth) {
1413        if (position == NO_POSITION) {
1414            return;
1415        }
1416
1417        mHeadersSupportFragment.setSelectedPosition(position, smooth);
1418        replaceMainFragment(position);
1419
1420        if (mMainFragmentRowsAdapter != null) {
1421            mMainFragmentRowsAdapter.setSelectedPosition(position, smooth);
1422        }
1423        mSelectedPosition = position;
1424
1425        updateTitleViewVisibility();
1426    }
1427
1428    private void replaceMainFragment(int position) {
1429        if (createMainFragment(mAdapter, position)) {
1430            swapToMainFragment();
1431            expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
1432            setupMainFragment();
1433            performPendingStates();
1434        }
1435    }
1436
1437    private void swapToMainFragment() {
1438        final VerticalGridView gridView = mHeadersSupportFragment.getVerticalGridView();
1439        if (isShowingHeaders() && gridView != null
1440                && gridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
1441            // if user is scrolling HeadersSupportFragment,  swap to empty fragment and wait scrolling
1442            // finishes.
1443            getChildFragmentManager().beginTransaction()
1444                    .replace(R.id.scale_frame, new Fragment()).commit();
1445            gridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
1446                @Override
1447                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
1448                    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
1449                        gridView.removeOnScrollListener(this);
1450                        FragmentManager fm = getChildFragmentManager();
1451                        Fragment currentFragment = fm.findFragmentById(R.id.scale_frame);
1452                        if (currentFragment != mMainFragment) {
1453                            fm.beginTransaction().replace(R.id.scale_frame, mMainFragment).commit();
1454                        }
1455                    }
1456                }
1457            });
1458        } else {
1459            // Otherwise swap immediately
1460            getChildFragmentManager().beginTransaction()
1461                    .replace(R.id.scale_frame, mMainFragment).commit();
1462        }
1463    }
1464
1465    /**
1466     * Sets the selected row position with smooth animation.
1467     */
1468    public void setSelectedPosition(int position) {
1469        setSelectedPosition(position, true);
1470    }
1471
1472    /**
1473     * Gets position of currently selected row.
1474     * @return Position of currently selected row.
1475     */
1476    public int getSelectedPosition() {
1477        return mSelectedPosition;
1478    }
1479
1480    /**
1481     * Sets the selected row position.
1482     */
1483    public void setSelectedPosition(int position, boolean smooth) {
1484        mSetSelectionRunnable.post(
1485                position, SetSelectionRunnable.TYPE_USER_REQUEST, smooth);
1486    }
1487
1488    /**
1489     * Selects a Row and perform an optional task on the Row. For example
1490     * <code>setSelectedPosition(10, true, new ListRowPresenterSelectItemViewHolderTask(5))</code>
1491     * scrolls to 11th row and selects 6th item on that row.  The method will be ignored if
1492     * RowsSupportFragment has not been created (i.e. before {@link #onCreateView(LayoutInflater,
1493     * ViewGroup, Bundle)}).
1494     *
1495     * @param rowPosition Which row to select.
1496     * @param smooth True to scroll to the row, false for no animation.
1497     * @param rowHolderTask Optional task to perform on the Row.  When the task is not null, headers
1498     * fragment will be collapsed.
1499     */
1500    public void setSelectedPosition(int rowPosition, boolean smooth,
1501            final Presenter.ViewHolderTask rowHolderTask) {
1502        if (mMainFragmentAdapterRegistry == null) {
1503            return;
1504        }
1505        if (rowHolderTask != null) {
1506            startHeadersTransition(false);
1507        }
1508        if (mMainFragmentRowsAdapter != null) {
1509            mMainFragmentRowsAdapter.setSelectedPosition(rowPosition, smooth, rowHolderTask);
1510        }
1511    }
1512
1513    @Override
1514    public void onStart() {
1515        super.onStart();
1516        mHeadersSupportFragment.setAlignment(mContainerListAlignTop);
1517        setMainFragmentAlignment();
1518
1519        if (mCanShowHeaders && mShowingHeaders && mHeadersSupportFragment.getView() != null) {
1520            mHeadersSupportFragment.getView().requestFocus();
1521        } else if ((!mCanShowHeaders || !mShowingHeaders) && mMainFragment.getView() != null) {
1522            mMainFragment.getView().requestFocus();
1523        }
1524
1525        if (mCanShowHeaders) {
1526            showHeaders(mShowingHeaders);
1527        }
1528
1529        if (isEntranceTransitionEnabled()) {
1530            setEntranceTransitionStartState();
1531        }
1532    }
1533
1534    private void onExpandTransitionStart(boolean expand, final Runnable callback) {
1535        if (expand) {
1536            callback.run();
1537            return;
1538        }
1539        // Run a "pre" layout when we go non-expand, in order to get the initial
1540        // positions of added rows.
1541        new ExpandPreLayout(callback, mMainFragmentAdapter, getView()).execute();
1542    }
1543
1544    private void setMainFragmentAlignment() {
1545        int alignOffset = mContainerListAlignTop;
1546        if (mMainFragmentScaleEnabled
1547                && mMainFragmentAdapter.isScalingEnabled()
1548                && mShowingHeaders) {
1549            alignOffset = (int) (alignOffset / mScaleFactor + 0.5f);
1550        }
1551        mMainFragmentAdapter.setAlignment(alignOffset);
1552    }
1553
1554    /**
1555     * Enables/disables headers transition on back key support. This is enabled by
1556     * default. The BrowseSupportFragment will add a back stack entry when headers are
1557     * showing. Running a headers transition when the back key is pressed only
1558     * works when the headers state is {@link #HEADERS_ENABLED} or
1559     * {@link #HEADERS_HIDDEN}.
1560     * <p>
1561     * NOTE: If an Activity has its own onBackPressed() handling, you must
1562     * disable this feature. You may use {@link #startHeadersTransition(boolean)}
1563     * and {@link BrowseTransitionListener} in your own back stack handling.
1564     */
1565    public final void setHeadersTransitionOnBackEnabled(boolean headersBackStackEnabled) {
1566        mHeadersBackStackEnabled = headersBackStackEnabled;
1567    }
1568
1569    /**
1570     * Returns true if headers transition on back key support is enabled.
1571     */
1572    public final boolean isHeadersTransitionOnBackEnabled() {
1573        return mHeadersBackStackEnabled;
1574    }
1575
1576    private void readArguments(Bundle args) {
1577        if (args == null) {
1578            return;
1579        }
1580        if (args.containsKey(ARG_TITLE)) {
1581            setTitle(args.getString(ARG_TITLE));
1582        }
1583        if (args.containsKey(ARG_HEADERS_STATE)) {
1584            setHeadersState(args.getInt(ARG_HEADERS_STATE));
1585        }
1586    }
1587
1588    /**
1589     * Sets the state for the headers column in the browse fragment. Must be one
1590     * of {@link #HEADERS_ENABLED}, {@link #HEADERS_HIDDEN}, or
1591     * {@link #HEADERS_DISABLED}.
1592     *
1593     * @param headersState The state of the headers for the browse fragment.
1594     */
1595    public void setHeadersState(int headersState) {
1596        if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) {
1597            throw new IllegalArgumentException("Invalid headers state: " + headersState);
1598        }
1599        if (DEBUG) Log.v(TAG, "setHeadersState " + headersState);
1600
1601        if (headersState != mHeadersState) {
1602            mHeadersState = headersState;
1603            switch (headersState) {
1604                case HEADERS_ENABLED:
1605                    mCanShowHeaders = true;
1606                    mShowingHeaders = true;
1607                    break;
1608                case HEADERS_HIDDEN:
1609                    mCanShowHeaders = true;
1610                    mShowingHeaders = false;
1611                    break;
1612                case HEADERS_DISABLED:
1613                    mCanShowHeaders = false;
1614                    mShowingHeaders = false;
1615                    break;
1616                default:
1617                    Log.w(TAG, "Unknown headers state: " + headersState);
1618                    break;
1619            }
1620            if (mHeadersSupportFragment != null) {
1621                mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
1622            }
1623        }
1624    }
1625
1626    /**
1627     * Returns the state of the headers column in the browse fragment.
1628     */
1629    public int getHeadersState() {
1630        return mHeadersState;
1631    }
1632
1633    @Override
1634    protected Object createEntranceTransition() {
1635        return TransitionHelper.loadTransition(getActivity(),
1636                R.transition.lb_browse_entrance_transition);
1637    }
1638
1639    @Override
1640    protected void runEntranceTransition(Object entranceTransition) {
1641        TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
1642    }
1643
1644    @Override
1645    protected void onEntranceTransitionPrepare() {
1646        mHeadersSupportFragment.onTransitionPrepare();
1647        // setEntranceTransitionStartState() might be called when mMainFragment is null,
1648        // make sure it is called.
1649        mMainFragmentAdapter.setEntranceTransitionState(false);
1650        mMainFragmentAdapter.onTransitionPrepare();
1651    }
1652
1653    @Override
1654    protected void onEntranceTransitionStart() {
1655        mHeadersSupportFragment.onTransitionStart();
1656        mMainFragmentAdapter.onTransitionStart();
1657    }
1658
1659    @Override
1660    protected void onEntranceTransitionEnd() {
1661        if (mMainFragmentAdapter != null) {
1662            mMainFragmentAdapter.onTransitionEnd();
1663        }
1664
1665        if (mHeadersSupportFragment != null) {
1666            mHeadersSupportFragment.onTransitionEnd();
1667        }
1668    }
1669
1670    void setSearchOrbViewOnScreen(boolean onScreen) {
1671        View searchOrbView = getTitleViewAdapter().getSearchAffordanceView();
1672        if (searchOrbView != null) {
1673            MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
1674            lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
1675            searchOrbView.setLayoutParams(lp);
1676        }
1677    }
1678
1679    void setEntranceTransitionStartState() {
1680        setHeadersOnScreen(false);
1681        setSearchOrbViewOnScreen(false);
1682        mMainFragmentAdapter.setEntranceTransitionState(false);
1683    }
1684
1685    void setEntranceTransitionEndState() {
1686        setHeadersOnScreen(mShowingHeaders);
1687        setSearchOrbViewOnScreen(true);
1688        mMainFragmentAdapter.setEntranceTransitionState(true);
1689    }
1690
1691    private class ExpandPreLayout implements ViewTreeObserver.OnPreDrawListener {
1692
1693        private final View mView;
1694        private final Runnable mCallback;
1695        private int mState;
1696        private MainFragmentAdapter mainFragmentAdapter;
1697
1698        final static int STATE_INIT = 0;
1699        final static int STATE_FIRST_DRAW = 1;
1700        final static int STATE_SECOND_DRAW = 2;
1701
1702        ExpandPreLayout(Runnable callback, MainFragmentAdapter adapter, View view) {
1703            mView = view;
1704            mCallback = callback;
1705            mainFragmentAdapter = adapter;
1706        }
1707
1708        void execute() {
1709            mView.getViewTreeObserver().addOnPreDrawListener(this);
1710            mainFragmentAdapter.setExpand(false);
1711            mState = STATE_INIT;
1712        }
1713
1714        @Override
1715        public boolean onPreDraw() {
1716            if (getView() == null || getActivity() == null) {
1717                mView.getViewTreeObserver().removeOnPreDrawListener(this);
1718                return true;
1719            }
1720            if (mState == STATE_INIT) {
1721                mainFragmentAdapter.setExpand(true);
1722                mState = STATE_FIRST_DRAW;
1723            } else if (mState == STATE_FIRST_DRAW) {
1724                mCallback.run();
1725                mView.getViewTreeObserver().removeOnPreDrawListener(this);
1726                mState = STATE_SECOND_DRAW;
1727            }
1728            return false;
1729        }
1730    }
1731}
1732