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.v17.leanback.R;
19import android.support.v17.leanback.transition.LeanbackTransitionHelper;
20import android.support.v17.leanback.transition.TransitionHelper;
21import android.support.v17.leanback.transition.TransitionListener;
22import android.support.v17.leanback.widget.BrowseFrameLayout;
23import android.support.v17.leanback.widget.HorizontalGridView;
24import android.support.v17.leanback.widget.ItemBridgeAdapter;
25import android.support.v17.leanback.widget.OnItemViewClickedListener;
26import android.support.v17.leanback.widget.OnItemViewSelectedListener;
27import android.support.v17.leanback.widget.Presenter;
28import android.support.v17.leanback.widget.PresenterSelector;
29import android.support.v17.leanback.widget.RowPresenter;
30import android.support.v17.leanback.widget.TitleView;
31import android.support.v17.leanback.widget.VerticalGridView;
32import android.support.v17.leanback.widget.Row;
33import android.support.v17.leanback.widget.ObjectAdapter;
34import android.support.v17.leanback.widget.OnItemSelectedListener;
35import android.support.v17.leanback.widget.OnItemClickedListener;
36import android.support.v17.leanback.widget.SearchOrbView;
37import android.support.v4.view.ViewCompat;
38import android.util.Log;
39import android.support.v4.app.FragmentActivity;
40import android.support.v4.app.Fragment;
41import android.support.v4.app.FragmentManager;
42import android.support.v4.app.FragmentManager.BackStackEntry;
43import android.content.Context;
44import android.content.res.TypedArray;
45import android.os.Bundle;
46import android.view.LayoutInflater;
47import android.view.View;
48import android.view.View.OnClickListener;
49import android.view.ViewGroup;
50import android.view.ViewGroup.MarginLayoutParams;
51import android.view.ViewTreeObserver;
52import android.graphics.Color;
53import android.graphics.Rect;
54import android.graphics.drawable.Drawable;
55
56import static android.support.v7.widget.RecyclerView.NO_POSITION;
57
58/**
59 * A fragment for creating Leanback browse screens. It is composed of a
60 * RowsSupportFragment and a HeadersSupportFragment.
61 * <p>
62 * A BrowseSupportFragment renders the elements of its {@link ObjectAdapter} as a set
63 * of rows in a vertical list. The elements in this adapter must be subclasses
64 * of {@link Row}.
65 * <p>
66 * The HeadersSupportFragment can be set to be either shown or hidden by default, or
67 * may be disabled entirely. See {@link #setHeadersState} for details.
68 * <p>
69 * By default the BrowseSupportFragment includes support for returning to the headers
70 * when the user presses Back. For Activities that customize {@link
71 * android.support.v4.app.FragmentActivity#onBackPressed()}, you must disable this default Back key support by
72 * calling {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and
73 * use {@link BrowseSupportFragment.BrowseTransitionListener} and
74 * {@link #startHeadersTransition(boolean)}.
75 */
76public class BrowseSupportFragment extends BaseSupportFragment {
77
78    // BUNDLE attribute for saving header show/hide status when backstack is used:
79    static final String HEADER_STACK_INDEX = "headerStackIndex";
80    // BUNDLE attribute for saving header show/hide status when backstack is not used:
81    static final String HEADER_SHOW = "headerShow";
82    // BUNDLE attribute for title is showing
83    static final String TITLE_SHOW = "titleShow";
84
85    final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
86        int mLastEntryCount;
87        int mIndexOfHeadersBackStack;
88
89        BackStackListener() {
90            mLastEntryCount = getFragmentManager().getBackStackEntryCount();
91            mIndexOfHeadersBackStack = -1;
92        }
93
94        void load(Bundle savedInstanceState) {
95            if (savedInstanceState != null) {
96                mIndexOfHeadersBackStack = savedInstanceState.getInt(HEADER_STACK_INDEX, -1);
97                mShowingHeaders = mIndexOfHeadersBackStack == -1;
98            } else {
99                if (!mShowingHeaders) {
100                    getFragmentManager().beginTransaction()
101                            .addToBackStack(mWithHeadersBackStackName).commit();
102                }
103            }
104        }
105
106        void save(Bundle outState) {
107            outState.putInt(HEADER_STACK_INDEX, mIndexOfHeadersBackStack);
108        }
109
110
111        @Override
112        public void onBackStackChanged() {
113            if (getFragmentManager() == null) {
114                Log.w(TAG, "getFragmentManager() is null, stack:", new Exception());
115                return;
116            }
117            int count = getFragmentManager().getBackStackEntryCount();
118            // if backstack is growing and last pushed entry is "headers" backstack,
119            // remember the index of the entry.
120            if (count > mLastEntryCount) {
121                BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1);
122                if (mWithHeadersBackStackName.equals(entry.getName())) {
123                    mIndexOfHeadersBackStack = count - 1;
124                }
125            } else if (count < mLastEntryCount) {
126                // if popped "headers" backstack, initiate the show header transition if needed
127                if (mIndexOfHeadersBackStack >= count) {
128                    mIndexOfHeadersBackStack = -1;
129                    if (!mShowingHeaders) {
130                        startHeadersTransitionInternal(true);
131                    }
132                }
133            }
134            mLastEntryCount = count;
135        }
136    }
137
138    /**
139     * Listener for transitions between browse headers and rows.
140     */
141    public static class BrowseTransitionListener {
142        /**
143         * Callback when headers transition starts.
144         *
145         * @param withHeaders True if the transition will result in headers
146         *        being shown, false otherwise.
147         */
148        public void onHeadersTransitionStart(boolean withHeaders) {
149        }
150        /**
151         * Callback when headers transition stops.
152         *
153         * @param withHeaders True if the transition will result in headers
154         *        being shown, false otherwise.
155         */
156        public void onHeadersTransitionStop(boolean withHeaders) {
157        }
158    }
159
160    private static final String TAG = "BrowseSupportFragment";
161
162    private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
163
164    private static boolean DEBUG = false;
165
166    /** The headers fragment is enabled and shown by default. */
167    public static final int HEADERS_ENABLED = 1;
168
169    /** The headers fragment is enabled and hidden by default. */
170    public static final int HEADERS_HIDDEN = 2;
171
172    /** The headers fragment is disabled and will never be shown. */
173    public static final int HEADERS_DISABLED = 3;
174
175    private static final float SLIDE_DISTANCE_FACTOR = 2;
176
177    private RowsSupportFragment mRowsSupportFragment;
178    private HeadersSupportFragment mHeadersSupportFragment;
179
180    private ObjectAdapter mAdapter;
181
182    private String mTitle;
183    private Drawable mBadgeDrawable;
184    private int mHeadersState = HEADERS_ENABLED;
185    private int mBrandColor = Color.TRANSPARENT;
186    private boolean mBrandColorSet;
187
188    private BrowseFrameLayout mBrowseFrame;
189    private TitleView mTitleView;
190    private boolean mShowingTitle = true;
191    private boolean mHeadersBackStackEnabled = true;
192    private String mWithHeadersBackStackName;
193    private boolean mShowingHeaders = true;
194    private boolean mCanShowHeaders = true;
195    private int mContainerListMarginStart;
196    private int mContainerListAlignTop;
197    private boolean mRowScaleEnabled = true;
198    private SearchOrbView.Colors mSearchAffordanceColors;
199    private boolean mSearchAffordanceColorSet;
200    private OnItemSelectedListener mExternalOnItemSelectedListener;
201    private OnClickListener mExternalOnSearchClickedListener;
202    private OnItemClickedListener mOnItemClickedListener;
203    private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
204    private OnItemViewClickedListener mOnItemViewClickedListener;
205    private int mSelectedPosition = -1;
206
207    private PresenterSelector mHeaderPresenterSelector;
208
209    // transition related:
210    private Object mSceneWithTitle;
211    private Object mSceneWithoutTitle;
212    private Object mSceneWithHeaders;
213    private Object mSceneWithoutHeaders;
214    private Object mSceneAfterEntranceTransition;
215    private Object mTitleUpTransition;
216    private Object mTitleDownTransition;
217    private Object mHeadersTransition;
218    private BackStackListener mBackStackChangedListener;
219    private BrowseTransitionListener mBrowseTransitionListener;
220
221    private static final String ARG_TITLE = BrowseSupportFragment.class.getCanonicalName() + ".title";
222    private static final String ARG_BADGE_URI = BrowseSupportFragment.class.getCanonicalName() + ".badge";
223    private static final String ARG_HEADERS_STATE =
224        BrowseSupportFragment.class.getCanonicalName() + ".headersState";
225
226    /**
227     * Create arguments for a browse fragment.
228     *
229     * @param args The Bundle to place arguments into, or null if the method
230     *        should return a new Bundle.
231     * @param title The title of the BrowseSupportFragment.
232     * @param headersState The initial state of the headers of the
233     *        BrowseSupportFragment. Must be one of {@link #HEADERS_ENABLED}, {@link
234     *        #HEADERS_HIDDEN}, or {@link #HEADERS_DISABLED}.
235     * @return A Bundle with the given arguments for creating a BrowseSupportFragment.
236     */
237    public static Bundle createArgs(Bundle args, String title, int headersState) {
238        if (args == null) {
239            args = new Bundle();
240        }
241        args.putString(ARG_TITLE, title);
242        args.putInt(ARG_HEADERS_STATE, headersState);
243        return args;
244    }
245
246    /**
247     * Sets the brand color for the browse fragment. The brand color is used as
248     * the primary color for UI elements in the browse fragment. For example,
249     * the background color of the headers fragment uses the brand color.
250     *
251     * @param color The color to use as the brand color of the fragment.
252     */
253    public void setBrandColor(int color) {
254        mBrandColor = color;
255        mBrandColorSet = true;
256
257        if (mHeadersSupportFragment != null) {
258            mHeadersSupportFragment.setBackgroundColor(mBrandColor);
259        }
260    }
261
262    /**
263     * Returns the brand color for the browse fragment.
264     * The default is transparent.
265     */
266    public int getBrandColor() {
267        return mBrandColor;
268    }
269
270    /**
271     * Sets the adapter containing the rows for the fragment.
272     *
273     * <p>The items referenced by the adapter must be be derived from
274     * {@link Row}. These rows will be used by the rows fragment and the headers
275     * fragment (if not disabled) to render the browse rows.
276     *
277     * @param adapter An ObjectAdapter for the browse rows. All items must
278     *        derive from {@link Row}.
279     */
280    public void setAdapter(ObjectAdapter adapter) {
281        mAdapter = adapter;
282        if (mRowsSupportFragment != null) {
283            mRowsSupportFragment.setAdapter(adapter);
284            mHeadersSupportFragment.setAdapter(adapter);
285        }
286    }
287
288    /**
289     * Returns the adapter containing the rows for the fragment.
290     */
291    public ObjectAdapter getAdapter() {
292        return mAdapter;
293    }
294
295    /**
296     * Sets an item selection listener. This listener will be called when an
297     * item or row is selected by a user.
298     *
299     * @param listener The listener to call when an item or row is selected.
300     * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
301     */
302    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
303        mExternalOnItemSelectedListener = listener;
304    }
305
306    /**
307     * Sets an item selection listener.
308     */
309    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
310        mExternalOnItemViewSelectedListener = listener;
311    }
312
313    /**
314     * Returns an item selection listener.
315     */
316    public OnItemViewSelectedListener getOnItemViewSelectedListener() {
317        return mExternalOnItemViewSelectedListener;
318    }
319
320    /**
321     * Sets an item clicked listener on the fragment.
322     *
323     * <p>OnItemClickedListener will override {@link View.OnClickListener} that
324     * an item presenter may set during
325     * {@link Presenter#onCreateViewHolder(ViewGroup)}. So in general, you
326     * should choose to use an {@link OnItemClickedListener} or a
327     * {@link View.OnClickListener} on your item views, but not both.
328     *
329     * @param listener The listener to call when an item is clicked.
330     * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
331     */
332    public void setOnItemClickedListener(OnItemClickedListener listener) {
333        mOnItemClickedListener = listener;
334        if (mRowsSupportFragment != null) {
335            mRowsSupportFragment.setOnItemClickedListener(listener);
336        }
337    }
338
339    /**
340     * Returns the item clicked listener.
341     * @deprecated Use {@link #getOnItemViewClickedListener()}
342     */
343    public OnItemClickedListener getOnItemClickedListener() {
344        return mOnItemClickedListener;
345    }
346
347    /**
348     * Sets an item clicked listener on the fragment.
349     * OnItemViewClickedListener will override {@link View.OnClickListener} that
350     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
351     * So in general,  developer should choose one of the listeners but not both.
352     */
353    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
354        mOnItemViewClickedListener = listener;
355        if (mRowsSupportFragment != null) {
356            mRowsSupportFragment.setOnItemViewClickedListener(listener);
357        }
358    }
359
360    /**
361     * Returns the item Clicked listener.
362     */
363    public OnItemViewClickedListener getOnItemViewClickedListener() {
364        return mOnItemViewClickedListener;
365    }
366
367    /**
368     * Sets a click listener for the search affordance.
369     *
370     * <p>The presence of a listener will change the visibility of the search
371     * affordance in the fragment title. When set to non-null, the title will
372     * contain an element that a user may click to begin a search.
373     *
374     * <p>The listener's {@link View.OnClickListener#onClick onClick} method
375     * will be invoked when the user clicks on the search element.
376     *
377     * @param listener The listener to call when the search element is clicked.
378     */
379    public void setOnSearchClickedListener(View.OnClickListener listener) {
380        mExternalOnSearchClickedListener = listener;
381        if (mTitleView != null) {
382            mTitleView.setOnSearchClickedListener(listener);
383        }
384    }
385
386    /**
387     * Sets the {@link SearchOrbView.Colors} used to draw the search affordance.
388     */
389    public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
390        mSearchAffordanceColors = colors;
391        mSearchAffordanceColorSet = true;
392        if (mTitleView != null) {
393            mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
394        }
395    }
396
397    /**
398     * Returns the {@link SearchOrbView.Colors} used to draw the search affordance.
399     */
400    public SearchOrbView.Colors getSearchAffordanceColors() {
401        if (mSearchAffordanceColorSet) {
402            return mSearchAffordanceColors;
403        }
404        if (mTitleView == null) {
405            throw new IllegalStateException("Fragment views not yet created");
406        }
407        return mTitleView.getSearchAffordanceColors();
408    }
409
410    /**
411     * Sets the color used to draw the search affordance.
412     * A default brighter color will be set by the framework.
413     *
414     * @param color The color to use for the search affordance.
415     */
416    public void setSearchAffordanceColor(int color) {
417        setSearchAffordanceColors(new SearchOrbView.Colors(color));
418    }
419
420    /**
421     * Returns the color used to draw the search affordance.
422     */
423    public int getSearchAffordanceColor() {
424        return getSearchAffordanceColors().color;
425    }
426
427    /**
428     * Start a headers transition.
429     *
430     * <p>This method will begin a transition to either show or hide the
431     * headers, depending on the value of withHeaders. If headers are disabled
432     * for this browse fragment, this method will throw an exception.
433     *
434     * @param withHeaders True if the headers should transition to being shown,
435     *        false if the transition should result in headers being hidden.
436     */
437    public void startHeadersTransition(boolean withHeaders) {
438        if (!mCanShowHeaders) {
439            throw new IllegalStateException("Cannot start headers transition");
440        }
441        if (isInHeadersTransition() || mShowingHeaders == withHeaders) {
442            return;
443        }
444        startHeadersTransitionInternal(withHeaders);
445    }
446
447    /**
448     * Returns true if the headers transition is currently running.
449     */
450    public boolean isInHeadersTransition() {
451        return mHeadersTransition != null;
452    }
453
454    /**
455     * Returns true if headers are shown.
456     */
457    public boolean isShowingHeaders() {
458        return mShowingHeaders;
459    }
460
461    /**
462     * Set a listener for browse fragment transitions.
463     *
464     * @param listener The listener to call when a browse headers transition
465     *        begins or ends.
466     */
467    public void setBrowseTransitionListener(BrowseTransitionListener listener) {
468        mBrowseTransitionListener = listener;
469    }
470
471    /**
472     * Enables scaling of rows when headers are present.
473     * By default enabled to increase density.
474     *
475     * @param enable true to enable row scaling
476     */
477    public void enableRowScaling(boolean enable) {
478        mRowScaleEnabled = enable;
479        if (mRowsSupportFragment != null) {
480            mRowsSupportFragment.enableRowScaling(mRowScaleEnabled);
481        }
482    }
483
484    private void startHeadersTransitionInternal(final boolean withHeaders) {
485        if (getFragmentManager().isDestroyed()) {
486            return;
487        }
488        mShowingHeaders = withHeaders;
489        mRowsSupportFragment.onExpandTransitionStart(!withHeaders, new Runnable() {
490            @Override
491            public void run() {
492                mHeadersSupportFragment.onTransitionStart();
493                createHeadersTransition();
494                if (mBrowseTransitionListener != null) {
495                    mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
496                }
497                sTransitionHelper.runTransition(withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders,
498                        mHeadersTransition);
499                if (mHeadersBackStackEnabled) {
500                    if (!withHeaders) {
501                        getFragmentManager().beginTransaction()
502                                .addToBackStack(mWithHeadersBackStackName).commit();
503                    } else {
504                        int index = mBackStackChangedListener.mIndexOfHeadersBackStack;
505                        if (index >= 0) {
506                            BackStackEntry entry = getFragmentManager().getBackStackEntryAt(index);
507                            getFragmentManager().popBackStackImmediate(entry.getId(),
508                                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
509                        }
510                    }
511                }
512            }
513        });
514    }
515
516    private boolean isVerticalScrolling() {
517        // don't run transition
518        return mHeadersSupportFragment.getVerticalGridView().getScrollState()
519                != HorizontalGridView.SCROLL_STATE_IDLE
520                || mRowsSupportFragment.getVerticalGridView().getScrollState()
521                != HorizontalGridView.SCROLL_STATE_IDLE;
522    }
523
524    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
525            new BrowseFrameLayout.OnFocusSearchListener() {
526        @Override
527        public View onFocusSearch(View focused, int direction) {
528            // if headers is running transition,  focus stays
529            if (mCanShowHeaders && isInHeadersTransition()) {
530                return focused;
531            }
532            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
533
534            final View searchOrbView = mTitleView.getSearchAffordanceView();
535            if (focused == searchOrbView && direction == View.FOCUS_DOWN) {
536                return mCanShowHeaders && mShowingHeaders ?
537                        mHeadersSupportFragment.getVerticalGridView() :
538                        mRowsSupportFragment.getVerticalGridView();
539            } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
540                    && direction == View.FOCUS_UP) {
541                return searchOrbView;
542            }
543
544            // If headers fragment is disabled, just return null.
545            if (!mCanShowHeaders) {
546                return null;
547            }
548            boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;
549            int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
550            int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
551            if (direction == towardStart) {
552                if (isVerticalScrolling() || mShowingHeaders) {
553                    return focused;
554                }
555                return mHeadersSupportFragment.getVerticalGridView();
556            } else if (direction == towardEnd) {
557                if (isVerticalScrolling() || !mShowingHeaders) {
558                    return focused;
559                }
560                return mRowsSupportFragment.getVerticalGridView();
561            } else {
562                return null;
563            }
564        }
565    };
566
567    private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener =
568            new BrowseFrameLayout.OnChildFocusListener() {
569
570        @Override
571        public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
572            if (getChildFragmentManager().isDestroyed()) {
573                return true;
574            }
575            // Make sure not changing focus when requestFocus() is called.
576            if (mCanShowHeaders && mShowingHeaders) {
577                if (mHeadersSupportFragment != null && mHeadersSupportFragment.getView() != null &&
578                        mHeadersSupportFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
579                    return true;
580                }
581            }
582            if (mRowsSupportFragment != null && mRowsSupportFragment.getView() != null &&
583                    mRowsSupportFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
584                return true;
585            }
586            if (mTitleView != null &&
587                    mTitleView.requestFocus(direction, previouslyFocusedRect)) {
588                return true;
589            }
590            return false;
591        };
592
593        @Override
594        public void onRequestChildFocus(View child, View focused) {
595            if (getChildFragmentManager().isDestroyed()) {
596                return;
597            }
598            if (!mCanShowHeaders || isInHeadersTransition()) return;
599            int childId = child.getId();
600            if (childId == R.id.browse_container_dock && mShowingHeaders) {
601                startHeadersTransitionInternal(false);
602            } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
603                startHeadersTransitionInternal(true);
604            }
605        }
606    };
607
608    @Override
609    public void onSaveInstanceState(Bundle outState) {
610        if (mBackStackChangedListener != null) {
611            mBackStackChangedListener.save(outState);
612        } else {
613            outState.putBoolean(HEADER_SHOW, mShowingHeaders);
614        }
615        outState.putBoolean(TITLE_SHOW, mShowingTitle);
616    }
617
618    @Override
619    public void onCreate(Bundle savedInstanceState) {
620        super.onCreate(savedInstanceState);
621        TypedArray ta = getActivity().obtainStyledAttributes(R.styleable.LeanbackTheme);
622        mContainerListMarginStart = (int) ta.getDimension(
623                R.styleable.LeanbackTheme_browseRowsMarginStart, 0);
624        mContainerListAlignTop = (int) ta.getDimension(
625                R.styleable.LeanbackTheme_browseRowsMarginTop, 0);
626        ta.recycle();
627
628        readArguments(getArguments());
629
630        if (mCanShowHeaders) {
631            if (mHeadersBackStackEnabled) {
632                mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
633                mBackStackChangedListener = new BackStackListener();
634                getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
635                mBackStackChangedListener.load(savedInstanceState);
636            } else {
637                if (savedInstanceState != null) {
638                    mShowingHeaders = savedInstanceState.getBoolean(HEADER_SHOW);
639                }
640            }
641        }
642
643    }
644
645    @Override
646    public void onDestroy() {
647        if (mBackStackChangedListener != null) {
648            getFragmentManager().removeOnBackStackChangedListener(mBackStackChangedListener);
649        }
650        super.onDestroy();
651    }
652
653    @Override
654    public View onCreateView(LayoutInflater inflater, ViewGroup container,
655            Bundle savedInstanceState) {
656        if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
657            mRowsSupportFragment = new RowsSupportFragment();
658            mHeadersSupportFragment = new HeadersSupportFragment();
659            getChildFragmentManager().beginTransaction()
660                    .replace(R.id.browse_headers_dock, mHeadersSupportFragment)
661                    .replace(R.id.browse_container_dock, mRowsSupportFragment).commit();
662        } else {
663            mHeadersSupportFragment = (HeadersSupportFragment) getChildFragmentManager()
664                    .findFragmentById(R.id.browse_headers_dock);
665            mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager()
666                    .findFragmentById(R.id.browse_container_dock);
667        }
668
669        mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
670
671        mRowsSupportFragment.setAdapter(mAdapter);
672        if (mHeaderPresenterSelector != null) {
673            mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
674        }
675        mHeadersSupportFragment.setAdapter(mAdapter);
676
677        mRowsSupportFragment.enableRowScaling(mRowScaleEnabled);
678        mRowsSupportFragment.setOnItemSelectedListener(mRowSelectedListener);
679        mRowsSupportFragment.setOnItemViewSelectedListener(mRowViewSelectedListener);
680        mHeadersSupportFragment.setOnItemSelectedListener(mHeaderSelectedListener);
681        mHeadersSupportFragment.setOnHeaderClickedListener(mHeaderClickedListener);
682        mRowsSupportFragment.setOnItemClickedListener(mOnItemClickedListener);
683        mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
684
685        View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
686
687        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
688        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
689        mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
690
691        mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
692        mTitleView.setTitle(mTitle);
693        mTitleView.setBadgeDrawable(mBadgeDrawable);
694        if (mSearchAffordanceColorSet) {
695            mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
696        }
697        if (mExternalOnSearchClickedListener != null) {
698            mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener);
699        }
700
701        if (mBrandColorSet) {
702            mHeadersSupportFragment.setBackgroundColor(mBrandColor);
703        }
704
705        mSceneWithTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
706            @Override
707            public void run() {
708                mTitleView.setVisibility(View.VISIBLE);
709            }
710        });
711        mSceneWithoutTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
712            @Override
713            public void run() {
714                mTitleView.setVisibility(View.INVISIBLE);
715            }
716        });
717        mSceneWithHeaders = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
718            @Override
719            public void run() {
720                showHeaders(true);
721            }
722        });
723        mSceneWithoutHeaders =  sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
724            @Override
725            public void run() {
726                showHeaders(false);
727            }
728        });
729        mSceneAfterEntranceTransition = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
730            @Override
731            public void run() {
732                setEntranceTransitionEndState();
733            }
734        });
735        Context context = getActivity();
736        mTitleUpTransition = LeanbackTransitionHelper.loadTitleOutTransition(context,
737                sTransitionHelper);
738        mTitleDownTransition = LeanbackTransitionHelper.loadTitleInTransition(context,
739                sTransitionHelper);
740
741        if (savedInstanceState != null) {
742            mShowingTitle = savedInstanceState.getBoolean(TITLE_SHOW);
743        }
744        mTitleView.setVisibility(mShowingTitle ? View.VISIBLE: View.INVISIBLE);
745
746        return root;
747    }
748
749    private void createHeadersTransition() {
750        mHeadersTransition = sTransitionHelper.loadTransition(getActivity(),
751                mShowingHeaders ?
752                R.transition.lb_browse_headers_in : R.transition.lb_browse_headers_out);
753
754        sTransitionHelper.setTransitionListener(mHeadersTransition, new TransitionListener() {
755            @Override
756            public void onTransitionStart(Object transition) {
757            }
758            @Override
759            public void onTransitionEnd(Object transition) {
760                mHeadersTransition = null;
761                mRowsSupportFragment.onTransitionEnd();
762                mHeadersSupportFragment.onTransitionEnd();
763                if (mShowingHeaders) {
764                    VerticalGridView headerGridView = mHeadersSupportFragment.getVerticalGridView();
765                    if (headerGridView != null && !headerGridView.hasFocus()) {
766                        headerGridView.requestFocus();
767                    }
768                } else {
769                    VerticalGridView rowsGridView = mRowsSupportFragment.getVerticalGridView();
770                    if (rowsGridView != null && !rowsGridView.hasFocus()) {
771                        rowsGridView.requestFocus();
772                    }
773                }
774                if (mBrowseTransitionListener != null) {
775                    mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
776                }
777            }
778        });
779    }
780
781    /**
782     * Sets the {@link PresenterSelector} used to render the row headers.
783     *
784     * @param headerPresenterSelector The PresenterSelector that will determine
785     *        the Presenter for each row header.
786     */
787    public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) {
788        mHeaderPresenterSelector = headerPresenterSelector;
789        if (mHeadersSupportFragment != null) {
790            mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
791        }
792    }
793
794    private void setRowsAlignedLeft(boolean alignLeft) {
795        MarginLayoutParams lp;
796        View containerList;
797        containerList = mRowsSupportFragment.getView();
798        lp = (MarginLayoutParams) containerList.getLayoutParams();
799        lp.setMarginStart(alignLeft ? 0 : mContainerListMarginStart);
800        containerList.setLayoutParams(lp);
801    }
802
803    private void setHeadersOnScreen(boolean onScreen) {
804        MarginLayoutParams lp;
805        View containerList;
806        containerList = mHeadersSupportFragment.getView();
807        lp = (MarginLayoutParams) containerList.getLayoutParams();
808        lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
809        containerList.setLayoutParams(lp);
810    }
811
812    private void showHeaders(boolean show) {
813        if (DEBUG) Log.v(TAG, "showHeaders " + show);
814        mHeadersSupportFragment.setHeadersEnabled(show);
815        setHeadersOnScreen(show);
816        setRowsAlignedLeft(!show);
817        mRowsSupportFragment.setExpand(!show);
818    }
819
820    private HeadersSupportFragment.OnHeaderClickedListener mHeaderClickedListener =
821        new HeadersSupportFragment.OnHeaderClickedListener() {
822            @Override
823            public void onHeaderClicked() {
824                if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
825                    return;
826                }
827                startHeadersTransitionInternal(false);
828                mRowsSupportFragment.getVerticalGridView().requestFocus();
829            }
830        };
831
832    private OnItemViewSelectedListener mRowViewSelectedListener = new OnItemViewSelectedListener() {
833        @Override
834        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
835                RowPresenter.ViewHolder rowViewHolder, Row row) {
836            int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
837            if (DEBUG) Log.v(TAG, "row selected position " + position);
838            onRowSelected(position);
839            if (mExternalOnItemViewSelectedListener != null) {
840                mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
841                        rowViewHolder, row);
842            }
843        }
844    };
845
846    private OnItemSelectedListener mRowSelectedListener = new OnItemSelectedListener() {
847        @Override
848        public void onItemSelected(Object item, Row row) {
849            if (mExternalOnItemSelectedListener != null) {
850                mExternalOnItemSelectedListener.onItemSelected(item, row);
851            }
852        }
853    };
854
855    private OnItemSelectedListener mHeaderSelectedListener = new OnItemSelectedListener() {
856        @Override
857        public void onItemSelected(Object item, Row row) {
858            int position = mHeadersSupportFragment.getVerticalGridView().getSelectedPosition();
859            if (DEBUG) Log.v(TAG, "header selected position " + position);
860            onRowSelected(position);
861        }
862    };
863
864    private void onRowSelected(int position) {
865        if (position != mSelectedPosition) {
866            mSetSelectionRunnable.mPosition = position;
867            mBrowseFrame.getHandler().post(mSetSelectionRunnable);
868
869            if (getAdapter() == null || getAdapter().size() == 0 || position == 0) {
870                if (!mShowingTitle) {
871                    sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition);
872                    mShowingTitle = true;
873                }
874            } else if (mShowingTitle) {
875                sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition);
876                mShowingTitle = false;
877            }
878        }
879    }
880
881    private class SetSelectionRunnable implements Runnable {
882        int mPosition;
883        boolean mSmooth = true;
884        @Override
885        public void run() {
886            setSelection(mPosition, mSmooth);
887        }
888    }
889
890    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
891
892    private void setSelection(int position, boolean smooth) {
893        if (position != NO_POSITION) {
894            mRowsSupportFragment.setSelectedPosition(position, smooth);
895            mHeadersSupportFragment.setSelectedPosition(position, smooth);
896        }
897        mSelectedPosition = position;
898    }
899
900    /**
901     * Sets the selected row position with smooth animation.
902     */
903    public void setSelectedPosition(int position) {
904        setSelectedPosition(position, true);
905    }
906
907    /**
908     * Sets the selected row position.
909     */
910    public void setSelectedPosition(int position, boolean smooth) {
911        mSetSelectionRunnable.mPosition = position;
912        mSetSelectionRunnable.mSmooth = smooth;
913        mBrowseFrame.getHandler().post(mSetSelectionRunnable);
914    }
915
916    @Override
917    public void onStart() {
918        super.onStart();
919        mHeadersSupportFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
920        mHeadersSupportFragment.setItemAlignment();
921        mRowsSupportFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
922        mRowsSupportFragment.setItemAlignment();
923
924        mRowsSupportFragment.setScalePivots(0, mContainerListAlignTop);
925
926        if (mCanShowHeaders && mShowingHeaders && mHeadersSupportFragment.getView() != null) {
927            mHeadersSupportFragment.getView().requestFocus();
928        } else if ((!mCanShowHeaders || !mShowingHeaders)
929                && mRowsSupportFragment.getView() != null) {
930            mRowsSupportFragment.getView().requestFocus();
931        }
932        if (mCanShowHeaders) {
933            showHeaders(mShowingHeaders);
934        }
935        if (isEntranceTransitionEnabled()) {
936            setEntranceTransitionStartState();
937        }
938    }
939
940    @Override
941    public void onPause() {
942        mTitleView.enableAnimation(false);
943        super.onPause();
944    }
945
946    @Override
947    public void onResume() {
948        super.onResume();
949        mTitleView.enableAnimation(true);
950    }
951
952    /**
953     * Enable/disable headers transition on back key support. This is enabled by
954     * default. The BrowseSupportFragment will add a back stack entry when headers are
955     * showing. Running a headers transition when the back key is pressed only
956     * works when the headers state is {@link #HEADERS_ENABLED} or
957     * {@link #HEADERS_HIDDEN}.
958     * <p>
959     * NOTE: If an Activity has its own onBackPressed() handling, you must
960     * disable this feature. You may use {@link #startHeadersTransition(boolean)}
961     * and {@link BrowseTransitionListener} in your own back stack handling.
962     */
963    public final void setHeadersTransitionOnBackEnabled(boolean headersBackStackEnabled) {
964        mHeadersBackStackEnabled = headersBackStackEnabled;
965    }
966
967    /**
968     * Returns true if headers transition on back key support is enabled.
969     */
970    public final boolean isHeadersTransitionOnBackEnabled() {
971        return mHeadersBackStackEnabled;
972    }
973
974    private void readArguments(Bundle args) {
975        if (args == null) {
976            return;
977        }
978        if (args.containsKey(ARG_TITLE)) {
979            setTitle(args.getString(ARG_TITLE));
980        }
981        if (args.containsKey(ARG_HEADERS_STATE)) {
982            setHeadersState(args.getInt(ARG_HEADERS_STATE));
983        }
984    }
985
986    /**
987     * Sets the drawable displayed in the browse fragment title.
988     *
989     * @param drawable The Drawable to display in the browse fragment title.
990     */
991    public void setBadgeDrawable(Drawable drawable) {
992        if (mBadgeDrawable != drawable) {
993            mBadgeDrawable = drawable;
994            if (mTitleView != null) {
995                mTitleView.setBadgeDrawable(drawable);
996            }
997        }
998    }
999
1000    /**
1001     * Returns the badge drawable used in the fragment title.
1002     */
1003    public Drawable getBadgeDrawable() {
1004        return mBadgeDrawable;
1005    }
1006
1007    /**
1008     * Sets a title for the browse fragment.
1009     *
1010     * @param title The title of the browse fragment.
1011     */
1012    public void setTitle(String title) {
1013        mTitle = title;
1014        if (mTitleView != null) {
1015            mTitleView.setTitle(title);
1016        }
1017    }
1018
1019    /**
1020     * Returns the title for the browse fragment.
1021     */
1022    public String getTitle() {
1023        return mTitle;
1024    }
1025
1026    /**
1027     * Sets the state for the headers column in the browse fragment. Must be one
1028     * of {@link #HEADERS_ENABLED}, {@link #HEADERS_HIDDEN}, or
1029     * {@link #HEADERS_DISABLED}.
1030     *
1031     * @param headersState The state of the headers for the browse fragment.
1032     */
1033    public void setHeadersState(int headersState) {
1034        if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) {
1035            throw new IllegalArgumentException("Invalid headers state: " + headersState);
1036        }
1037        if (DEBUG) Log.v(TAG, "setHeadersState " + headersState);
1038
1039        if (headersState != mHeadersState) {
1040            mHeadersState = headersState;
1041            switch (headersState) {
1042                case HEADERS_ENABLED:
1043                    mCanShowHeaders = true;
1044                    mShowingHeaders = true;
1045                    break;
1046                case HEADERS_HIDDEN:
1047                    mCanShowHeaders = true;
1048                    mShowingHeaders = false;
1049                    break;
1050                case HEADERS_DISABLED:
1051                    mCanShowHeaders = false;
1052                    mShowingHeaders = false;
1053                    break;
1054                default:
1055                    Log.w(TAG, "Unknown headers state: " + headersState);
1056                    break;
1057            }
1058            if (mHeadersSupportFragment != null) {
1059                mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
1060            }
1061        }
1062    }
1063
1064    /**
1065     * Returns the state of the headers column in the browse fragment.
1066     */
1067    public int getHeadersState() {
1068        return mHeadersState;
1069    }
1070
1071    @Override
1072    protected Object createEntranceTransition() {
1073        return sTransitionHelper.loadTransition(getActivity(),
1074                R.transition.lb_browse_entrance_transition);
1075    }
1076
1077    @Override
1078    protected void runEntranceTransition(Object entranceTransition) {
1079        sTransitionHelper.runTransition(mSceneAfterEntranceTransition,
1080                entranceTransition);
1081    }
1082
1083    @Override
1084    protected void onEntranceTransitionStart() {
1085        mHeadersSupportFragment.onTransitionStart();
1086        mRowsSupportFragment.onTransitionStart();
1087    }
1088
1089    @Override
1090    protected void onEntranceTransitionEnd() {
1091        mRowsSupportFragment.onTransitionEnd();
1092        mHeadersSupportFragment.onTransitionEnd();
1093    }
1094
1095    void setSearchOrbViewOnScreen(boolean onScreen) {
1096        View searchOrbView = mTitleView.getSearchAffordanceView();
1097        MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
1098        lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
1099        searchOrbView.setLayoutParams(lp);
1100    }
1101
1102    void setEntranceTransitionStartState() {
1103        setHeadersOnScreen(false);
1104        setSearchOrbViewOnScreen(false);
1105        mRowsSupportFragment.setEntranceTransitionState(false);
1106    }
1107
1108    void setEntranceTransitionEndState() {
1109        setHeadersOnScreen(mShowingHeaders);
1110        setSearchOrbViewOnScreen(true);
1111        mRowsSupportFragment.setEntranceTransitionState(true);
1112    }
1113
1114}
1115
1116