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