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