BrowseFragment.java revision 4cf79b1c4d38a190317961891f9fd052836710fd
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.app;
15
16import android.support.v17.leanback.R;
17import android.support.v17.leanback.widget.HorizontalGridView;
18import android.support.v17.leanback.widget.Presenter;
19import android.support.v17.leanback.widget.PresenterSelector;
20import android.support.v17.leanback.widget.VerticalGridView;
21import android.support.v17.leanback.widget.Row;
22import android.support.v17.leanback.widget.ObjectAdapter;
23import android.support.v17.leanback.widget.OnItemSelectedListener;
24import android.support.v17.leanback.widget.OnItemClickedListener;
25import android.support.v17.leanback.widget.SearchOrbView;
26import android.support.v7.widget.RecyclerView;
27import android.util.Log;
28import android.util.SparseIntArray;
29import android.util.TypedValue;
30import android.app.Fragment;
31import android.content.res.TypedArray;
32import android.os.Bundle;
33import android.view.LayoutInflater;
34import android.view.View;
35import android.view.View.OnClickListener;
36import android.view.ViewGroup;
37import android.view.ViewGroup.MarginLayoutParams;
38import android.widget.ImageView;
39import android.widget.TextView;
40import android.graphics.Color;
41import android.graphics.drawable.Drawable;
42
43import java.util.ArrayList;
44
45import static android.support.v7.widget.RecyclerView.NO_POSITION;
46
47/**
48 * Wrapper fragment for leanback browse screens. Composed of a
49 * RowsFragment and a HeadersFragment.
50 *
51 */
52public class BrowseFragment extends Fragment {
53    private static final String TAG = "BrowseFragment";
54    private static boolean DEBUG = false;
55
56    /** The fastlane navigation panel is enabled and shown by default. */
57    public static final int HEADERS_ENABLED = 1;
58
59    /** The fastlane navigation panel is enabled and hidden by default. */
60    public static final int HEADERS_HIDDEN = 2;
61
62    /** The fastlane navigation panel is disabled and will never be shown. */
63    public static final int HEADERS_DISABLED = 3;
64
65    private static final float SLIDE_DISTANCE_FACTOR = 2;
66
67    private RowsFragment mRowsFragment;
68    private HeadersFragment mHeadersFragment;
69
70    private ObjectAdapter mAdapter;
71
72    private Params mParams;
73    private int mBrandColor = Color.TRANSPARENT;
74    private boolean mBrandColorSet;
75
76    private BrowseFrameLayout mBrowseFrame;
77    private ImageView mBadgeView;
78    private TextView mTitleView;
79    private ViewGroup mBrowseTitle;
80    private SearchOrbView mSearchOrbView;
81    private boolean mShowingTitle = true;
82    private boolean mShowingHeaders = true;
83    private boolean mCanShowHeaders = true;
84    private int mContainerListMarginLeft;
85    private int mContainerListAlignTop;
86    private int mSearchAffordanceColor;
87    private boolean mSearchAffordanceColorSet;
88    private OnItemSelectedListener mExternalOnItemSelectedListener;
89    private OnClickListener mExternalOnSearchClickedListener;
90    private OnItemClickedListener mOnItemClickedListener;
91    private int mSelectedPosition = -1;
92
93    private PresenterSelector mHeaderPresenterSelector;
94
95    // transition related:
96    private static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
97    private int mReparentHeaderId = View.generateViewId();
98    private Object mSceneWithTitle;
99    private Object mSceneWithoutTitle;
100    private Object mSceneWithHeaders;
101    private Object mSceneWithoutHeaders;
102    private Object mTitleTransition;
103    private Object mHeadersTransition;
104    private int mHeadersTransitionStartDelay;
105    private int mHeadersTransitionDuration;
106
107    private static final String ARG_TITLE = BrowseFragment.class.getCanonicalName() + ".title";
108    private static final String ARG_BADGE_URI = BrowseFragment.class.getCanonicalName() + ".badge";
109    private static final String ARG_HEADERS_STATE =
110        BrowseFragment.class.getCanonicalName() + ".headersState";
111
112    /**
113     * @param args Bundle to use for the arguments, if null a new Bundle will be created.
114     */
115    public static Bundle createArgs(Bundle args, String title, String badgeUri) {
116        return createArgs(args, title, badgeUri, HEADERS_ENABLED);
117    }
118
119    public static Bundle createArgs(Bundle args, String title, String badgeUri, int headersState) {
120        if (args == null) {
121            args = new Bundle();
122        }
123        args.putString(ARG_TITLE, title);
124        args.putString(ARG_BADGE_URI, badgeUri);
125        args.putInt(ARG_HEADERS_STATE, headersState);
126        return args;
127    }
128
129    public static class Params {
130        private String mTitle;
131        private Drawable mBadgeDrawable;
132        private int mHeadersState;
133
134        /**
135         * Sets the badge image.
136         */
137        public void setBadgeImage(Drawable drawable) {
138            mBadgeDrawable = drawable;
139        }
140
141        /**
142         * Returns the badge image.
143         */
144        public Drawable getBadgeImage() {
145            return mBadgeDrawable;
146        }
147
148        /**
149         * Sets a title for the browse fragment.
150         */
151        public void setTitle(String title) {
152            mTitle = title;
153        }
154
155        /**
156         * Returns the title for the browse fragment.
157         */
158        public String getTitle() {
159            return mTitle;
160        }
161
162        /**
163         * Sets the state for the headers column in the browse fragment.
164         */
165        public void setHeadersState(int headersState) {
166            if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) {
167                Log.e(TAG, "Invalid headers state: " + headersState
168                        + ", default to enabled and shown.");
169                mHeadersState = HEADERS_ENABLED;
170            } else {
171                mHeadersState = headersState;
172            }
173        }
174
175        /**
176         * Returns the state for the headers column in the browse fragment.
177         */
178        public int getHeadersState() {
179            return mHeadersState;
180        }
181    }
182
183    /**
184     * Set browse parameters.
185     */
186    public void setBrowseParams(Params params) {
187        mParams = params;
188        setBadgeDrawable(mParams.mBadgeDrawable);
189        setTitle(mParams.mTitle);
190        setHeadersState(mParams.mHeadersState);
191    }
192
193    /**
194     * Returns browse parameters.
195     */
196    public Params getBrowseParams() {
197        return mParams;
198    }
199
200    /**
201     * Sets the brand color for the browse fragment.
202     */
203    public void setBrandColor(int color) {
204        mBrandColor = color;
205        mBrandColorSet = true;
206
207        if (mHeadersFragment != null) {
208            mHeadersFragment.setBackgroundColor(mBrandColor);
209        }
210    }
211
212    /**
213     * Returns the brand color for the browse fragment.
214     * The default is transparent.
215     */
216    public int getBrandColor() {
217        return mBrandColor;
218    }
219
220    /**
221     * Sets the list of rows for the fragment.
222     */
223    public void setAdapter(ObjectAdapter adapter) {
224        mAdapter = adapter;
225        if (mRowsFragment != null) {
226            mRowsFragment.setAdapter(adapter);
227            mHeadersFragment.setAdapter(adapter);
228        }
229    }
230
231    /**
232     * Returns the list of rows.
233     */
234    public ObjectAdapter getAdapter() {
235        return mAdapter;
236    }
237
238    /**
239     * Sets an item selection listener.
240     */
241    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
242        mExternalOnItemSelectedListener = listener;
243    }
244
245    /**
246     * Sets an item clicked listener on the fragment.
247     * OnItemClickedListener will override {@link View.OnClickListener} that
248     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
249     * So in general,  developer should choose one of the listeners but not both.
250     */
251    public void setOnItemClickedListener(OnItemClickedListener listener) {
252        mOnItemClickedListener = listener;
253        if (mRowsFragment != null) {
254            mRowsFragment.setOnItemClickedListener(listener);
255        }
256    }
257
258    /**
259     * Returns the item Clicked listener.
260     */
261    public OnItemClickedListener getOnItemClickedListener() {
262        return mOnItemClickedListener;
263    }
264
265    /**
266     * Sets a click listener for the search affordance.
267     *
268     * The presence of a listener will change the visibility of the search affordance in the
269     * title area. When set to non-null the title area will contain a call to search action.
270     *
271     * The listener onClick method will be invoked when the user click on the search action.
272     *
273     * @param listener The listener.
274     */
275    public void setOnSearchClickedListener(View.OnClickListener listener) {
276        mExternalOnSearchClickedListener = listener;
277        if (mSearchOrbView != null) {
278            mSearchOrbView.setOnOrbClickedListener(listener);
279        }
280    }
281
282    /**
283     * Sets the color used to draw the search affordance.
284     */
285    public void setSearchAffordanceColor(int color) {
286        mSearchAffordanceColor = color;
287        mSearchAffordanceColorSet = true;
288
289        if (mSearchOrbView != null) {
290            mSearchOrbView.setOrbColor(mSearchAffordanceColor);
291        }
292    }
293
294    /**
295     * Returns the color used to draw the search affordance.
296     * Can be called only after an activity has been attached.
297     */
298    public int getSearchAffordanceColor() {
299        if (getActivity() == null) {
300            throw new IllegalStateException("Activity must be attached");
301        }
302
303        if (mSearchAffordanceColorSet) {
304            return mSearchAffordanceColor;
305        }
306
307        TypedValue outValue = new TypedValue();
308        getActivity().getTheme().resolveAttribute(android.R.attr.colorForeground, outValue, true);
309        return getResources().getColor(outValue.resourceId);
310    }
311
312    private void onHeadersTransitionStart(boolean withHeaders) {
313        mRowsFragment.onTransitionStart();
314        mHeadersFragment.onTransitionStart();
315        createHeadersTransition(withHeaders);
316    }
317
318    private boolean isVerticalScrolling() {
319        // don't run transition
320        return mHeadersFragment.getVerticalGridView().getScrollState()
321                != HorizontalGridView.SCROLL_STATE_IDLE
322                || mRowsFragment.getVerticalGridView().getScrollState()
323                != HorizontalGridView.SCROLL_STATE_IDLE;
324    }
325
326    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
327            new BrowseFrameLayout.OnFocusSearchListener() {
328        @Override
329        public View onFocusSearch(View focused, int direction) {
330            // If fastlane is disabled, just return null.
331            if (!mCanShowHeaders) return null;
332
333            // if fast lane is running transition,  focus stays
334            if (mHeadersTransition != null) return focused;
335            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
336            if (direction == View.FOCUS_LEFT) {
337                if (isVerticalScrolling() || mShowingHeaders) {
338                    return focused;
339                }
340                return mHeadersFragment.getVerticalGridView();
341            } else if (direction == View.FOCUS_RIGHT) {
342                if (isVerticalScrolling() || !mShowingHeaders) {
343                    return focused;
344                }
345                return mRowsFragment.getVerticalGridView();
346            } else if (focused == mSearchOrbView && direction == View.FOCUS_DOWN) {
347                return mShowingHeaders ? mHeadersFragment.getVerticalGridView() :
348                    mRowsFragment.getVerticalGridView();
349
350            } else if (focused != mSearchOrbView && mSearchOrbView.getVisibility() == View.VISIBLE
351                    && direction == View.FOCUS_UP) {
352                return mSearchOrbView;
353
354            } else {
355                return null;
356            }
357        }
358    };
359
360    private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener =
361            new BrowseFrameLayout.OnChildFocusListener() {
362        @Override
363        public void onRequestChildFocus(View child, View focused) {
364            int childId = child.getId();
365            if (mHeadersTransition != null) return;
366            if (childId == R.id.browse_container_dock && mShowingHeaders) {
367                mShowingHeaders = false;
368                onHeadersTransitionStart(false);
369                sTransitionHelper.runTransition(mSceneWithoutHeaders, mHeadersTransition);
370            } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
371                mShowingHeaders = true;
372                //mHeadersFragment.getView().setAlpha(1f);
373                onHeadersTransitionStart(true);
374                sTransitionHelper.runTransition(mSceneWithHeaders, mHeadersTransition);
375            }
376        }
377    };
378
379    @Override
380    public void onCreate(Bundle savedInstanceState) {
381        super.onCreate(savedInstanceState);
382        TypedArray ta = getActivity().obtainStyledAttributes(R.styleable.LeanbackTheme);
383        mContainerListMarginLeft = (int) ta.getDimension(
384                R.styleable.LeanbackTheme_browseRowsMarginStart, 0);
385        mContainerListAlignTop = (int) ta.getDimension(
386                R.styleable.LeanbackTheme_browseRowsMarginTop, 0);
387        ta.recycle();
388
389        mHeadersTransitionStartDelay = getResources()
390                .getInteger(R.integer.lb_browse_headers_transition_delay);
391        mHeadersTransitionDuration = getResources()
392                .getInteger(R.integer.lb_browse_headers_transition_duration);
393    }
394
395    @Override
396    public View onCreateView(LayoutInflater inflater, ViewGroup container,
397            Bundle savedInstanceState) {
398        if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
399            mRowsFragment = new RowsFragment();
400            mHeadersFragment = new HeadersFragment();
401            getChildFragmentManager().beginTransaction()
402                    .replace(R.id.browse_headers_dock, mHeadersFragment)
403                    .replace(R.id.browse_container_dock, mRowsFragment).commit();
404        } else {
405            mHeadersFragment = (HeadersFragment) getChildFragmentManager()
406                    .findFragmentById(R.id.browse_headers_dock);
407            mRowsFragment = (RowsFragment) getChildFragmentManager()
408                    .findFragmentById(R.id.browse_container_dock);
409        }
410        mRowsFragment.setReparentHeaderId(mReparentHeaderId);
411        mRowsFragment.setAdapter(mAdapter);
412        if (mHeaderPresenterSelector != null) {
413            mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector);
414        }
415        mHeadersFragment.setReparentHeaderId(mReparentHeaderId);
416        mHeadersFragment.setAdapter(mAdapter);
417
418        mRowsFragment.setOnItemSelectedListener(mRowSelectedListener);
419        mHeadersFragment.setOnItemSelectedListener(mHeaderSelectedListener);
420        mHeadersFragment.setOnHeaderClickedListener(mHeaderClickedListener);
421        mRowsFragment.setOnItemClickedListener(mOnItemClickedListener);
422
423        View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
424
425        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
426        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
427        mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
428
429        mBrowseTitle = (ViewGroup) root.findViewById(R.id.browse_title_group);
430        mBadgeView = (ImageView) mBrowseTitle.findViewById(R.id.browse_badge);
431        mTitleView = (TextView) mBrowseTitle.findViewById(R.id.browse_title);
432        mSearchOrbView = (SearchOrbView) mBrowseTitle.findViewById(R.id.browse_orb);
433        mSearchOrbView.setOrbColor(getSearchAffordanceColor());
434        if (mExternalOnSearchClickedListener != null) {
435            mSearchOrbView.setOnOrbClickedListener(mExternalOnSearchClickedListener);
436        }
437
438        readArguments(getArguments());
439        if (mParams != null) {
440            setBadgeDrawable(mParams.mBadgeDrawable);
441            setTitle(mParams.mTitle);
442            setHeadersState(mParams.mHeadersState);
443            if (mBrandColorSet) {
444                mHeadersFragment.setBackgroundColor(mBrandColor);
445            }
446        }
447
448        mSceneWithTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
449            @Override
450            public void run() {
451                showTitle(true);
452            }
453        });
454        mSceneWithoutTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
455            @Override
456            public void run() {
457                showTitle(false);
458            }
459        });
460        mSceneWithHeaders = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
461            @Override
462            public void run() {
463                showHeaders(true);
464            }
465        });
466        mSceneWithoutHeaders =  sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
467            @Override
468            public void run() {
469                showHeaders(false);
470            }
471        });
472        mTitleTransition = sTransitionHelper.createAutoTransition();
473        sTransitionHelper.excludeChildren(mTitleTransition, R.id.browse_headers, true);
474        sTransitionHelper.excludeChildren(mTitleTransition, R.id.container_list, true);
475
476        return root;
477    }
478
479    private void createHeadersTransition(boolean withHeaders) {
480        mHeadersTransition = sTransitionHelper.createTransitionSet(false);
481        sTransitionHelper.excludeChildren(mHeadersTransition, R.id.browse_title_group, true);
482        Object changeBounds = sTransitionHelper.createChangeBounds(true);
483        Object fadeIn = sTransitionHelper.createFadeTransition(TransitionHelper.FADE_IN);
484        Object fadeOut = sTransitionHelper.createFadeTransition(TransitionHelper.FADE_OUT);
485
486        sTransitionHelper.exclude(fadeIn, mReparentHeaderId, true);
487        sTransitionHelper.exclude(fadeOut, mReparentHeaderId, true);
488        if (!withHeaders) {
489            sTransitionHelper.setChangeBoundsDefaultStartDelay(changeBounds,
490                    mHeadersTransitionStartDelay);
491        }
492        sTransitionHelper.setChangeBoundsStartDelay(changeBounds, mReparentHeaderId,
493                withHeaders ? mHeadersTransitionStartDelay : 0);
494
495        final int selectedPosition = mSelectedPosition;
496        Object slide = sTransitionHelper.createSlide(new SlideCallback() {
497            @Override
498            public boolean getSlide(View view, boolean appear, int[] edge, float[] distance) {
499                // we only care about the view with specific transition position Tag.
500                Integer position = (Integer) view.getTag(R.id.lb_header_transition_position);
501                if (position == null) {
502                    return false;
503                }
504                distance[0] = view.getHeight() * SLIDE_DISTANCE_FACTOR;
505                if (position < selectedPosition) {
506                    edge[0] = TransitionHelper.SLIDE_TOP;
507                    return true;
508                } else if (position > selectedPosition) {
509                    edge[0] = TransitionHelper.SLIDE_BOTTOM;
510                    return true;
511                }
512                return false;
513            }
514        });
515        sTransitionHelper.exclude(slide, mReparentHeaderId, true);
516        sTransitionHelper.setDuration(slide, mHeadersTransitionDuration);
517        if (withHeaders) {
518            sTransitionHelper.setStartDelay(slide, mHeadersTransitionStartDelay);
519        }
520        sTransitionHelper.addTransition(mHeadersTransition, slide);
521
522        sTransitionHelper.setDuration(fadeOut, mHeadersTransitionDuration);
523        sTransitionHelper.addTransition(mHeadersTransition, fadeOut);
524        sTransitionHelper.setDuration(changeBounds, mHeadersTransitionDuration);
525        sTransitionHelper.addTransition(mHeadersTransition, changeBounds);
526        sTransitionHelper.setDuration(fadeIn, mHeadersTransitionDuration);
527        sTransitionHelper.setStartDelay(fadeIn, mHeadersTransitionStartDelay);
528        sTransitionHelper.addTransition(mHeadersTransition, fadeIn);
529
530        sTransitionHelper.setTransitionListener(mHeadersTransition, new TransitionListener() {
531            @Override
532            public void onTransitionStart(Object transition) {
533            }
534            @Override
535            public void onTransitionEnd(Object transition) {
536                mHeadersTransition = null;
537                mRowsFragment.onTransitionEnd();
538                mHeadersFragment.onTransitionEnd();
539                if (mShowingHeaders) {
540                    VerticalGridView headerGridView = mHeadersFragment.getVerticalGridView();
541                    if (headerGridView != null && !headerGridView.hasFocus()) {
542                        headerGridView.requestFocus();
543                    }
544                } else {
545                    VerticalGridView rowsGridView = mRowsFragment.getVerticalGridView();
546                    if (rowsGridView != null && !rowsGridView.hasFocus()) {
547                        rowsGridView.requestFocus();
548                    }
549                }
550            }
551        });
552    }
553
554    public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) {
555        mHeaderPresenterSelector = headerPresenterSelector;
556        if (mHeadersFragment != null) {
557            mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector);
558        }
559    }
560
561    private void showTitle(boolean show) {
562        mBrowseTitle.setVisibility(show ? View.VISIBLE : View.GONE);
563    }
564
565    private void showHeaders(boolean show) {
566        if (DEBUG) Log.v(TAG, "showHeaders " + show);
567        mHeadersFragment.setHeadersVisiblity(show);
568        MarginLayoutParams lp;
569        View containerList;
570
571        containerList = mRowsFragment.getView();
572        lp = (MarginLayoutParams) containerList.getLayoutParams();
573        lp.leftMargin = show ? mContainerListMarginLeft : 0;
574        containerList.setLayoutParams(lp);
575
576        containerList = mHeadersFragment.getView();
577        lp = (MarginLayoutParams) containerList.getLayoutParams();
578        lp.leftMargin = show ? 0 : -mContainerListMarginLeft;
579        containerList.setLayoutParams(lp);
580
581        mRowsFragment.setExpand(!show);
582    }
583
584    private HeadersFragment.OnHeaderClickedListener mHeaderClickedListener =
585        new HeadersFragment.OnHeaderClickedListener() {
586            @Override
587            public void onHeaderClicked() {
588                if (!mCanShowHeaders || !mShowingHeaders) return;
589
590                if (mHeadersTransition != null) {
591                    return;
592                }
593                mShowingHeaders = false;
594                onHeadersTransitionStart(false);
595                sTransitionHelper.runTransition(mSceneWithoutHeaders, mHeadersTransition);
596                mRowsFragment.getVerticalGridView().requestFocus();
597            }
598        };
599
600    private OnItemSelectedListener mRowSelectedListener = new OnItemSelectedListener() {
601        @Override
602        public void onItemSelected(Object item, Row row) {
603            int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
604            if (DEBUG) Log.v(TAG, "row selected position " + position);
605            onRowSelected(position);
606            if (mExternalOnItemSelectedListener != null) {
607                mExternalOnItemSelectedListener.onItemSelected(item, row);
608            }
609        }
610    };
611
612    private OnItemSelectedListener mHeaderSelectedListener = new OnItemSelectedListener() {
613        @Override
614        public void onItemSelected(Object item, Row row) {
615            int position = mHeadersFragment.getVerticalGridView().getSelectedPosition();
616            if (DEBUG) Log.v(TAG, "header selected position " + position);
617            onRowSelected(position);
618        }
619    };
620
621    private void onRowSelected(int position) {
622        if (position != mSelectedPosition) {
623            mSetSelectionRunnable.mPosition = position;
624            mBrowseFrame.getHandler().post(mSetSelectionRunnable);
625
626            if (position == 0) {
627                if (!mShowingTitle) {
628                    sTransitionHelper.runTransition(mSceneWithTitle, mTitleTransition);
629                    mShowingTitle = true;
630                }
631            } else if (mShowingTitle) {
632                sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleTransition);
633                mShowingTitle = false;
634            }
635        }
636    }
637
638    private class SetSelectionRunnable implements Runnable {
639        int mPosition;
640        @Override
641        public void run() {
642            setSelection(mPosition);
643        }
644    }
645
646    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
647
648    private void setSelection(int position) {
649        if (position != NO_POSITION) {
650            mRowsFragment.setSelectedPosition(position);
651            mHeadersFragment.setSelectedPosition(position);
652        }
653        mSelectedPosition = position;
654    }
655
656    private void setVerticalGridViewLayout(VerticalGridView listview, int extraOffset) {
657        // align the top edge of item to a fixed position
658        listview.setItemAlignmentOffset(0);
659        listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
660        listview.setWindowAlignmentOffset(mContainerListAlignTop + extraOffset);
661        listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
662        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
663    }
664
665    /**
666     * Setup dimensions that are only meaningful when the child Fragments are inside
667     * BrowseFragment.
668     */
669    private void setupChildFragmentsLayout() {
670        VerticalGridView headerList = mHeadersFragment.getVerticalGridView();
671        VerticalGridView containerList = mRowsFragment.getVerticalGridView();
672
673        // Both fragments list view has the same alignment
674        setVerticalGridViewLayout(headerList, 16);
675        setVerticalGridViewLayout(containerList, 0);
676    }
677
678    @Override
679    public void onStart() {
680        super.onStart();
681        setupChildFragmentsLayout();
682        if (mCanShowHeaders && mShowingHeaders && mHeadersFragment.getView() != null) {
683            mHeadersFragment.getView().requestFocus();
684        } else if ((!mCanShowHeaders || !mShowingHeaders)
685                && mRowsFragment.getView() != null) {
686            mRowsFragment.getView().requestFocus();
687        }
688        showHeaders(mCanShowHeaders && mShowingHeaders);
689    }
690
691    private void readArguments(Bundle args) {
692        if (args == null) {
693            return;
694        }
695        if (args.containsKey(ARG_TITLE)) {
696            setTitle(args.getString(ARG_TITLE));
697        }
698
699        if (args.containsKey(ARG_BADGE_URI)) {
700            setBadgeUri(args.getString(ARG_BADGE_URI));
701        }
702
703        if (args.containsKey(ARG_HEADERS_STATE)) {
704            setHeadersState(args.getInt(ARG_HEADERS_STATE));
705        }
706    }
707
708    private void setBadgeUri(String badgeUri) {
709        // TODO - need a drawable downloader
710    }
711
712    private void setBadgeDrawable(Drawable drawable) {
713        if (mBadgeView == null) {
714            return;
715        }
716        mBadgeView.setImageDrawable(drawable);
717        if (drawable != null) {
718            mBadgeView.setVisibility(View.VISIBLE);
719            mTitleView.setVisibility(View.GONE);
720        } else {
721            mBadgeView.setVisibility(View.GONE);
722            mTitleView.setVisibility(View.VISIBLE);
723        }
724    }
725
726    private void setTitle(String title) {
727        if (mTitleView != null) {
728            mTitleView.setText(title);
729        }
730    }
731
732    private void setHeadersState(int headersState) {
733        if (DEBUG) Log.v(TAG, "setHeadersState " + headersState);
734        switch (headersState) {
735            case HEADERS_ENABLED:
736                mCanShowHeaders = true;
737                mShowingHeaders = true;
738                break;
739            case HEADERS_HIDDEN:
740                mCanShowHeaders = true;
741                mShowingHeaders = false;
742                break;
743            case HEADERS_DISABLED:
744                mCanShowHeaders = false;
745                mShowingHeaders = false;
746                break;
747            default:
748                Log.w(TAG, "Unknown headers state: " + headersState);
749                break;
750        }
751    }
752}
753