• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // CHECKSTYLE:OFF Generated code
2 /* This file is auto-generated from DetailsFragment.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  */
17 package android.support.v17.leanback.app;
18 
19 import android.support.v4.app.FragmentActivity;
20 import android.support.v4.app.Fragment;
21 import android.support.v4.app.FragmentTransaction;
22 import android.graphics.Rect;
23 import android.graphics.drawable.Drawable;
24 import android.os.Build;
25 import android.os.Bundle;
26 import android.support.annotation.CallSuper;
27 import android.support.v17.leanback.R;
28 import android.support.v17.leanback.transition.TransitionHelper;
29 import android.support.v17.leanback.transition.TransitionListener;
30 import android.support.v17.leanback.util.StateMachine.Event;
31 import android.support.v17.leanback.util.StateMachine.State;
32 import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
33 import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
34 import android.support.v17.leanback.widget.BrowseFrameLayout;
35 import android.support.v17.leanback.widget.DetailsParallax;
36 import android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter;
37 import android.support.v17.leanback.widget.ItemAlignmentFacet;
38 import android.support.v17.leanback.widget.ItemBridgeAdapter;
39 import android.support.v17.leanback.widget.ObjectAdapter;
40 import android.support.v17.leanback.widget.Presenter;
41 import android.support.v17.leanback.widget.PresenterSelector;
42 import android.support.v17.leanback.widget.RowPresenter;
43 import android.support.v17.leanback.widget.VerticalGridView;
44 import android.util.Log;
45 import android.view.KeyEvent;
46 import android.view.LayoutInflater;
47 import android.view.View;
48 import android.view.ViewGroup;
49 import android.view.Window;
50 
51 import java.lang.ref.WeakReference;
52 
53 /**
54  * A fragment for creating Leanback details screens.
55  *
56  * <p>
57  * A DetailsSupportFragment renders the elements of its {@link ObjectAdapter} as a set
58  * of rows in a vertical list.The Adapter's {@link PresenterSelector} must maintain subclasses
59  * of {@link RowPresenter}.
60  * </p>
61  *
62  * When {@link FullWidthDetailsOverviewRowPresenter} is found in adapter,  DetailsSupportFragment will
63  * setup default behavior of the DetailsOverviewRow:
64  * <li>
65  * The alignment of FullWidthDetailsOverviewRowPresenter is setup in
66  * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}.
67  * </li>
68  * <li>
69  * The view status switching of FullWidthDetailsOverviewRowPresenter is done in
70  * {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter,
71  * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)}.
72  * </li>
73  *
74  * <p>
75  * The recommended activity themes to use with a DetailsSupportFragment are
76  * <li>
77  * {@link android.support.v17.leanback.R.style#Theme_Leanback_Details} with activity
78  * shared element transition for {@link FullWidthDetailsOverviewRowPresenter}.
79  * </li>
80  * <li>
81  * {@link android.support.v17.leanback.R.style#Theme_Leanback_Details_NoSharedElementTransition}
82  * if shared element transition is not needed, for example if first row is not rendered by
83  * {@link FullWidthDetailsOverviewRowPresenter}.
84  * </li>
85  * </p>
86  *
87  * <p>
88  * DetailsSupportFragment can use {@link DetailsSupportFragmentBackgroundController} to add a parallax drawable
89  * background and embedded video playing fragment.
90  * </p>
91  */
92 public class DetailsSupportFragment extends BaseSupportFragment {
93     static final String TAG = "DetailsSupportFragment";
94     static boolean DEBUG = false;
95 
96     final State STATE_SET_ENTRANCE_START_STATE = new State("STATE_SET_ENTRANCE_START_STATE") {
97         @Override
98         public void run() {
99             mRowsSupportFragment.setEntranceTransitionState(false);
100         }
101     };
102 
103     final State STATE_ENTER_TRANSITION_INIT = new State("STATE_ENTER_TRANSIITON_INIT");
104 
switchToVideoBeforeVideoSupportFragmentCreated()105     void switchToVideoBeforeVideoSupportFragmentCreated() {
106         // if the video fragment is not ready: immediately fade out covering drawable,
107         // hide title and mark mPendingFocusOnVideo and set focus on it later.
108         mDetailsBackgroundController.switchToVideoBeforeCreate();
109         showTitle(false);
110         mPendingFocusOnVideo = true;
111         slideOutGridView();
112     }
113 
114     final State STATE_SWITCH_TO_VIDEO_IN_ON_CREATE = new State("STATE_SWITCH_TO_VIDEO_IN_ON_CREATE",
115             false, false) {
116         @Override
117         public void run() {
118             switchToVideoBeforeVideoSupportFragmentCreated();
119         }
120     };
121 
122     final State STATE_ENTER_TRANSITION_CANCEL = new State("STATE_ENTER_TRANSITION_CANCEL",
123             false, false) {
124         @Override
125         public void run() {
126             if (mWaitEnterTransitionTimeout != null) {
127                 mWaitEnterTransitionTimeout.mRef.clear();
128             }
129             // clear the activity enter/sharedElement transition, return transitions are kept.
130             // keep the return transitions and clear enter transition
131             if (getActivity() != null) {
132                 Window window = getActivity().getWindow();
133                 Object returnTransition = TransitionHelper.getReturnTransition(window);
134                 Object sharedReturnTransition = TransitionHelper
135                         .getSharedElementReturnTransition(window);
136                 TransitionHelper.setEnterTransition(window, null);
137                 TransitionHelper.setSharedElementEnterTransition(window, null);
138                 TransitionHelper.setReturnTransition(window, returnTransition);
139                 TransitionHelper.setSharedElementReturnTransition(window, sharedReturnTransition);
140             }
141         }
142     };
143 
144     final State STATE_ENTER_TRANSITION_COMPLETE = new State("STATE_ENTER_TRANSIITON_COMPLETE",
145             true, false);
146 
147     final State STATE_ENTER_TRANSITION_ADDLISTENER = new State("STATE_ENTER_TRANSITION_PENDING") {
148         @Override
149         public void run() {
150             Object transition = TransitionHelper.getEnterTransition(getActivity().getWindow());
151             TransitionHelper.addTransitionListener(transition, mEnterTransitionListener);
152         }
153     };
154 
155     final State STATE_ENTER_TRANSITION_PENDING = new State("STATE_ENTER_TRANSITION_PENDING") {
156         @Override
157         public void run() {
158             if (mWaitEnterTransitionTimeout == null) {
159                 new WaitEnterTransitionTimeout(DetailsSupportFragment.this);
160             }
161         }
162     };
163 
164     /**
165      * Start this task when first DetailsOverviewRow is created, if there is no entrance transition
166      * started, it will clear PF_ENTRANCE_TRANSITION_PENDING.
167      */
168     static class WaitEnterTransitionTimeout implements Runnable {
169         static final long WAIT_ENTERTRANSITION_START = 200;
170 
171         final WeakReference<DetailsSupportFragment> mRef;
172 
WaitEnterTransitionTimeout(DetailsSupportFragment f)173         WaitEnterTransitionTimeout(DetailsSupportFragment f) {
174             mRef = new WeakReference<>(f);
175             f.getView().postDelayed(this, WAIT_ENTERTRANSITION_START);
176         }
177 
178         @Override
run()179         public void run() {
180             DetailsSupportFragment f = mRef.get();
181             if (f != null) {
182                 f.mStateMachine.fireEvent(f.EVT_ENTER_TRANSIITON_DONE);
183             }
184         }
185     }
186 
187     final State STATE_ON_SAFE_START = new State("STATE_ON_SAFE_START") {
188         @Override
189         public void run() {
190             onSafeStart();
191         }
192     };
193 
194     final Event EVT_ONSTART = new Event("onStart");
195 
196     final Event EVT_NO_ENTER_TRANSITION = new Event("EVT_NO_ENTER_TRANSITION");
197 
198     final Event EVT_DETAILS_ROW_LOADED = new Event("onFirstRowLoaded");
199 
200     final Event EVT_ENTER_TRANSIITON_DONE = new Event("onEnterTransitionDone");
201 
202     final Event EVT_SWITCH_TO_VIDEO = new Event("switchToVideo");
203 
204     @Override
createStateMachineStates()205     void createStateMachineStates() {
206         super.createStateMachineStates();
207         mStateMachine.addState(STATE_SET_ENTRANCE_START_STATE);
208         mStateMachine.addState(STATE_ON_SAFE_START);
209         mStateMachine.addState(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE);
210         mStateMachine.addState(STATE_ENTER_TRANSITION_INIT);
211         mStateMachine.addState(STATE_ENTER_TRANSITION_ADDLISTENER);
212         mStateMachine.addState(STATE_ENTER_TRANSITION_CANCEL);
213         mStateMachine.addState(STATE_ENTER_TRANSITION_PENDING);
214         mStateMachine.addState(STATE_ENTER_TRANSITION_COMPLETE);
215     }
216 
217     @Override
createStateMachineTransitions()218     void createStateMachineTransitions() {
219         super.createStateMachineTransitions();
220         /**
221          * Part 1: Processing enter transitions after fragment.onCreate
222          */
223         mStateMachine.addTransition(STATE_START, STATE_ENTER_TRANSITION_INIT, EVT_ON_CREATE);
224         // if transition is not supported, skip to complete
225         mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE,
226                 COND_TRANSITION_NOT_SUPPORTED);
227         // if transition is not set on Activity, skip to complete
228         mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE,
229                 EVT_NO_ENTER_TRANSITION);
230         // if switchToVideo is called before EVT_ON_CREATEVIEW, clear enter transition and skip to
231         // complete.
232         mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_CANCEL,
233                 EVT_SWITCH_TO_VIDEO);
234         mStateMachine.addTransition(STATE_ENTER_TRANSITION_CANCEL, STATE_ENTER_TRANSITION_COMPLETE);
235         // once after onCreateView, we cannot skip the enter transition, add a listener and wait
236         // it to finish
237         mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_ADDLISTENER,
238                 EVT_ON_CREATEVIEW);
239         // when enter transition finishes, go to complete, however this might never happen if
240         // the activity is not giving transition options in startActivity, there is no API to query
241         // if this activity is started in a enter transition mode. So we rely on a timer below:
242         mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER,
243                 STATE_ENTER_TRANSITION_COMPLETE, EVT_ENTER_TRANSIITON_DONE);
244         // we are expecting app to start delayed enter transition shortly after details row is
245         // loaded, so create a timer and wait for enter transition start.
246         mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER,
247                 STATE_ENTER_TRANSITION_PENDING, EVT_DETAILS_ROW_LOADED);
248         // if enter transition not started in the timer, skip to DONE, this can be also true when
249         // startActivity is not giving transition option.
250         mStateMachine.addTransition(STATE_ENTER_TRANSITION_PENDING, STATE_ENTER_TRANSITION_COMPLETE,
251                 EVT_ENTER_TRANSIITON_DONE);
252 
253         /**
254          * Part 2: modification to the entrance transition defined in BaseSupportFragment
255          */
256         // Must finish enter transition before perform entrance transition.
257         mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ENTRANCE_PERFORM);
258         // Calling switch to video would hide immediately and skip entrance transition
259         mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE,
260                 EVT_SWITCH_TO_VIDEO);
261         mStateMachine.addTransition(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE, STATE_ENTRANCE_COMPLETE);
262         // if the entrance transition is skipped to complete by COND_TRANSITION_NOT_SUPPORTED, we
263         // still need to do the switchToVideo.
264         mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE,
265                 EVT_SWITCH_TO_VIDEO);
266 
267         // for once the view is created in onStart and prepareEntranceTransition was called, we
268         // could setEntranceStartState:
269         mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
270                 STATE_SET_ENTRANCE_START_STATE, EVT_ONSTART);
271 
272         /**
273          * Part 3: onSafeStart()
274          */
275         // for onSafeStart: the condition is onStart called, entrance transition complete
276         mStateMachine.addTransition(STATE_START, STATE_ON_SAFE_START, EVT_ONSTART);
277         mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_ON_SAFE_START);
278         mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ON_SAFE_START);
279     }
280 
281     private class SetSelectionRunnable implements Runnable {
282         int mPosition;
283         boolean mSmooth = true;
284 
SetSelectionRunnable()285         SetSelectionRunnable() {
286         }
287 
288         @Override
run()289         public void run() {
290             if (mRowsSupportFragment == null) {
291                 return;
292             }
293             mRowsSupportFragment.setSelectedPosition(mPosition, mSmooth);
294         }
295     }
296 
297     TransitionListener mEnterTransitionListener = new TransitionListener() {
298         @Override
299         public void onTransitionStart(Object transition) {
300             if (mWaitEnterTransitionTimeout != null) {
301                 // cancel task of WaitEnterTransitionTimeout, we will clearPendingEnterTransition
302                 // when transition finishes.
303                 mWaitEnterTransitionTimeout.mRef.clear();
304             }
305         }
306 
307         @Override
308         public void onTransitionCancel(Object transition) {
309             mStateMachine.fireEvent(EVT_ENTER_TRANSIITON_DONE);
310         }
311 
312         @Override
313         public void onTransitionEnd(Object transition) {
314             mStateMachine.fireEvent(EVT_ENTER_TRANSIITON_DONE);
315         }
316     };
317 
318     TransitionListener mReturnTransitionListener = new TransitionListener() {
319         @Override
320         public void onTransitionStart(Object transition) {
321             onReturnTransitionStart();
322         }
323     };
324 
325     BrowseFrameLayout mRootView;
326     View mBackgroundView;
327     Drawable mBackgroundDrawable;
328     Fragment mVideoSupportFragment;
329     DetailsParallax mDetailsParallax;
330     RowsSupportFragment mRowsSupportFragment;
331     ObjectAdapter mAdapter;
332     int mContainerListAlignTop;
333     BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener;
334     BaseOnItemViewClickedListener mOnItemViewClickedListener;
335     DetailsSupportFragmentBackgroundController mDetailsBackgroundController;
336 
337     // A temporarily flag when switchToVideo() is called in onCreate(), if mPendingFocusOnVideo is
338     // true, we will focus to VideoSupportFragment immediately after video fragment's view is created.
339     boolean mPendingFocusOnVideo = false;
340 
341     WaitEnterTransitionTimeout mWaitEnterTransitionTimeout;
342 
343     Object mSceneAfterEntranceTransition;
344 
345     final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
346 
347     final BaseOnItemViewSelectedListener<Object> mOnItemViewSelectedListener =
348             new BaseOnItemViewSelectedListener<Object>() {
349         @Override
350         public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
351                                    RowPresenter.ViewHolder rowViewHolder, Object row) {
352             int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
353             int subposition = mRowsSupportFragment.getVerticalGridView().getSelectedSubPosition();
354             if (DEBUG) Log.v(TAG, "row selected position " + position
355                     + " subposition " + subposition);
356             onRowSelected(position, subposition);
357             if (mExternalOnItemViewSelectedListener != null) {
358                 mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
359                         rowViewHolder, row);
360             }
361         }
362     };
363 
364     /**
365      * Sets the list of rows for the fragment.
366      */
setAdapter(ObjectAdapter adapter)367     public void setAdapter(ObjectAdapter adapter) {
368         mAdapter = adapter;
369         Presenter[] presenters = adapter.getPresenterSelector().getPresenters();
370         if (presenters != null) {
371             for (int i = 0; i < presenters.length; i++) {
372                 setupPresenter(presenters[i]);
373             }
374         } else {
375             Log.e(TAG, "PresenterSelector.getPresenters() not implemented");
376         }
377         if (mRowsSupportFragment != null) {
378             mRowsSupportFragment.setAdapter(adapter);
379         }
380     }
381 
382     /**
383      * Returns the list of rows.
384      */
getAdapter()385     public ObjectAdapter getAdapter() {
386         return mAdapter;
387     }
388 
389     /**
390      * Sets an item selection listener.
391      */
setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener)392     public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
393         mExternalOnItemViewSelectedListener = listener;
394     }
395 
396     /**
397      * Sets an item clicked listener.
398      */
setOnItemViewClickedListener(BaseOnItemViewClickedListener listener)399     public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
400         if (mOnItemViewClickedListener != listener) {
401             mOnItemViewClickedListener = listener;
402             if (mRowsSupportFragment != null) {
403                 mRowsSupportFragment.setOnItemViewClickedListener(listener);
404             }
405         }
406     }
407 
408     /**
409      * Returns the item clicked listener.
410      */
getOnItemViewClickedListener()411     public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
412         return mOnItemViewClickedListener;
413     }
414 
415     @Override
onCreate(Bundle savedInstanceState)416     public void onCreate(Bundle savedInstanceState) {
417         super.onCreate(savedInstanceState);
418         mContainerListAlignTop =
419             getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top);
420 
421         FragmentActivity activity = getActivity();
422         if (activity != null) {
423             Object transition = TransitionHelper.getEnterTransition(activity.getWindow());
424             if (transition == null) {
425                 mStateMachine.fireEvent(EVT_NO_ENTER_TRANSITION);
426             }
427             transition = TransitionHelper.getReturnTransition(activity.getWindow());
428             if (transition != null) {
429                 TransitionHelper.addTransitionListener(transition, mReturnTransitionListener);
430             }
431         } else {
432             mStateMachine.fireEvent(EVT_NO_ENTER_TRANSITION);
433         }
434     }
435 
436     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)437     public View onCreateView(LayoutInflater inflater, ViewGroup container,
438             Bundle savedInstanceState) {
439         mRootView = (BrowseFrameLayout) inflater.inflate(
440                 R.layout.lb_details_fragment, container, false);
441         mBackgroundView = mRootView.findViewById(R.id.details_background_view);
442         if (mBackgroundView != null) {
443             mBackgroundView.setBackground(mBackgroundDrawable);
444         }
445         mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager().findFragmentById(
446                 R.id.details_rows_dock);
447         if (mRowsSupportFragment == null) {
448             mRowsSupportFragment = new RowsSupportFragment();
449             getChildFragmentManager().beginTransaction()
450                     .replace(R.id.details_rows_dock, mRowsSupportFragment).commit();
451         }
452         installTitleView(inflater, mRootView, savedInstanceState);
453         mRowsSupportFragment.setAdapter(mAdapter);
454         mRowsSupportFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
455         mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
456 
457         mSceneAfterEntranceTransition = TransitionHelper.createScene(mRootView, new Runnable() {
458             @Override
459             public void run() {
460                 mRowsSupportFragment.setEntranceTransitionState(true);
461             }
462         });
463 
464         setupDpadNavigation();
465 
466         if (Build.VERSION.SDK_INT >= 21) {
467             // Setup adapter listener to work with ParallaxTransition (>= API 21).
468             mRowsSupportFragment.setExternalAdapterListener(new ItemBridgeAdapter.AdapterListener() {
469                 @Override
470                 public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
471                     if (mDetailsParallax != null && vh.getViewHolder()
472                             instanceof FullWidthDetailsOverviewRowPresenter.ViewHolder) {
473                         FullWidthDetailsOverviewRowPresenter.ViewHolder rowVh =
474                                 (FullWidthDetailsOverviewRowPresenter.ViewHolder)
475                                         vh.getViewHolder();
476                         rowVh.getOverviewView().setTag(R.id.lb_parallax_source,
477                                 mDetailsParallax);
478                     }
479                 }
480             });
481         }
482         return mRootView;
483     }
484 
485     /**
486      * @deprecated override {@link #onInflateTitleView(LayoutInflater,ViewGroup,Bundle)} instead.
487      */
488     @Deprecated
inflateTitle(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)489     protected View inflateTitle(LayoutInflater inflater, ViewGroup parent,
490             Bundle savedInstanceState) {
491         return super.onInflateTitleView(inflater, parent, savedInstanceState);
492     }
493 
494     @Override
onInflateTitleView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)495     public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
496                                    Bundle savedInstanceState) {
497         return inflateTitle(inflater, parent, savedInstanceState);
498     }
499 
setVerticalGridViewLayout(VerticalGridView listview)500     void setVerticalGridViewLayout(VerticalGridView listview) {
501         // align the top edge of item to a fixed position
502         listview.setItemAlignmentOffset(-mContainerListAlignTop);
503         listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
504         listview.setWindowAlignmentOffset(0);
505         listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
506         listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
507     }
508 
509     /**
510      * Called to setup each Presenter of Adapter passed in {@link #setAdapter(ObjectAdapter)}.Note
511      * that setup should only change the Presenter behavior that is meaningful in DetailsSupportFragment.
512      * For example how a row is aligned in details Fragment.   The default implementation invokes
513      * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}
514      *
515      */
setupPresenter(Presenter rowPresenter)516     protected void setupPresenter(Presenter rowPresenter) {
517         if (rowPresenter instanceof FullWidthDetailsOverviewRowPresenter) {
518             setupDetailsOverviewRowPresenter((FullWidthDetailsOverviewRowPresenter) rowPresenter);
519         }
520     }
521 
522     /**
523      * Called to setup {@link FullWidthDetailsOverviewRowPresenter}.  The default implementation
524      * adds two alignment positions({@link ItemAlignmentFacet}) for ViewHolder of
525      * FullWidthDetailsOverviewRowPresenter to align in fragment.
526      */
setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter presenter)527     protected void setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter presenter) {
528         ItemAlignmentFacet facet = new ItemAlignmentFacet();
529         // by default align details_frame to half window height
530         ItemAlignmentFacet.ItemAlignmentDef alignDef1 = new ItemAlignmentFacet.ItemAlignmentDef();
531         alignDef1.setItemAlignmentViewId(R.id.details_frame);
532         alignDef1.setItemAlignmentOffset(- getResources()
533                 .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_actions));
534         alignDef1.setItemAlignmentOffsetPercent(0);
535         // when description is selected, align details_frame to top edge
536         ItemAlignmentFacet.ItemAlignmentDef alignDef2 = new ItemAlignmentFacet.ItemAlignmentDef();
537         alignDef2.setItemAlignmentViewId(R.id.details_frame);
538         alignDef2.setItemAlignmentFocusViewId(R.id.details_overview_description);
539         alignDef2.setItemAlignmentOffset(- getResources()
540                 .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_description));
541         alignDef2.setItemAlignmentOffsetPercent(0);
542         ItemAlignmentFacet.ItemAlignmentDef[] defs =
543                 new ItemAlignmentFacet.ItemAlignmentDef[] {alignDef1, alignDef2};
544         facet.setAlignmentDefs(defs);
545         presenter.setFacet(ItemAlignmentFacet.class, facet);
546     }
547 
getVerticalGridView()548     VerticalGridView getVerticalGridView() {
549         return mRowsSupportFragment == null ? null : mRowsSupportFragment.getVerticalGridView();
550     }
551 
552     /**
553      * Gets embedded RowsSupportFragment showing multiple rows for DetailsSupportFragment.  If view of
554      * DetailsSupportFragment is not created, the method returns null.
555      * @return Embedded RowsSupportFragment showing multiple rows for DetailsSupportFragment.
556      */
getRowsSupportFragment()557     public RowsSupportFragment getRowsSupportFragment() {
558         return mRowsSupportFragment;
559     }
560 
561     /**
562      * Setup dimensions that are only meaningful when the child Fragments are inside
563      * DetailsSupportFragment.
564      */
setupChildFragmentLayout()565     private void setupChildFragmentLayout() {
566         setVerticalGridViewLayout(mRowsSupportFragment.getVerticalGridView());
567     }
568 
569     /**
570      * Sets the selected row position with smooth animation.
571      */
setSelectedPosition(int position)572     public void setSelectedPosition(int position) {
573         setSelectedPosition(position, true);
574     }
575 
576     /**
577      * Sets the selected row position.
578      */
setSelectedPosition(int position, boolean smooth)579     public void setSelectedPosition(int position, boolean smooth) {
580         mSetSelectionRunnable.mPosition = position;
581         mSetSelectionRunnable.mSmooth = smooth;
582         if (getView() != null && getView().getHandler() != null) {
583             getView().getHandler().post(mSetSelectionRunnable);
584         }
585     }
586 
switchToVideo()587     void switchToVideo() {
588         if (mVideoSupportFragment != null && mVideoSupportFragment.getView() != null) {
589             mVideoSupportFragment.getView().requestFocus();
590         } else {
591             mStateMachine.fireEvent(EVT_SWITCH_TO_VIDEO);
592         }
593     }
594 
switchToRows()595     void switchToRows() {
596         mPendingFocusOnVideo = false;
597         VerticalGridView verticalGridView = getVerticalGridView();
598         if (verticalGridView != null && verticalGridView.getChildCount() > 0) {
599             verticalGridView.requestFocus();
600         }
601     }
602 
603     /**
604      * This method asks DetailsSupportFragmentBackgroundController to add a fragment for rendering video.
605      * In case the fragment is already there, it will return the existing one. The method must be
606      * called after calling super.onCreate(). App usually does not call this method directly.
607      *
608      * @return Fragment the added or restored fragment responsible for rendering video.
609      * @see DetailsSupportFragmentBackgroundController#onCreateVideoSupportFragment()
610      */
findOrCreateVideoSupportFragment()611     final Fragment findOrCreateVideoSupportFragment() {
612         if (mVideoSupportFragment != null) {
613             return mVideoSupportFragment;
614         }
615         Fragment fragment = getChildFragmentManager()
616                 .findFragmentById(R.id.video_surface_container);
617         if (fragment == null && mDetailsBackgroundController != null) {
618             FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
619             ft2.add(android.support.v17.leanback.R.id.video_surface_container,
620                     fragment = mDetailsBackgroundController.onCreateVideoSupportFragment());
621             ft2.commit();
622             if (mPendingFocusOnVideo) {
623                 // wait next cycle for Fragment view created so we can focus on it.
624                 // This is a bit hack eventually we will do commitNow() which get view immediately.
625                 getView().post(new Runnable() {
626                     @Override
627                     public void run() {
628                         if (getView() != null) {
629                             switchToVideo();
630                         }
631                         mPendingFocusOnVideo = false;
632                     }
633                 });
634             }
635         }
636         mVideoSupportFragment = fragment;
637         return mVideoSupportFragment;
638     }
639 
onRowSelected(int selectedPosition, int selectedSubPosition)640     void onRowSelected(int selectedPosition, int selectedSubPosition) {
641         ObjectAdapter adapter = getAdapter();
642         if (( mRowsSupportFragment != null && mRowsSupportFragment.getView() != null
643                 && mRowsSupportFragment.getView().hasFocus() && !mPendingFocusOnVideo)
644                 && (adapter == null || adapter.size() == 0
645                 || (getVerticalGridView().getSelectedPosition() == 0
646                 && getVerticalGridView().getSelectedSubPosition() == 0))) {
647             showTitle(true);
648         } else {
649             showTitle(false);
650         }
651         if (adapter != null && adapter.size() > selectedPosition) {
652             final VerticalGridView gridView = getVerticalGridView();
653             final int count = gridView.getChildCount();
654             if (count > 0) {
655                 mStateMachine.fireEvent(EVT_DETAILS_ROW_LOADED);
656             }
657             for (int i = 0; i < count; i++) {
658                 ItemBridgeAdapter.ViewHolder bridgeViewHolder = (ItemBridgeAdapter.ViewHolder)
659                         gridView.getChildViewHolder(gridView.getChildAt(i));
660                 RowPresenter rowPresenter = (RowPresenter) bridgeViewHolder.getPresenter();
661                 onSetRowStatus(rowPresenter,
662                         rowPresenter.getRowViewHolder(bridgeViewHolder.getViewHolder()),
663                         bridgeViewHolder.getAdapterPosition(),
664                         selectedPosition, selectedSubPosition);
665             }
666         }
667     }
668 
669     /**
670      * Called when onStart and enter transition (postponed/none postponed) and entrance transition
671      * are all finished.
672      */
673     @CallSuper
onSafeStart()674     void onSafeStart() {
675         if (mDetailsBackgroundController != null) {
676             mDetailsBackgroundController.onStart();
677         }
678     }
679 
680     @CallSuper
onReturnTransitionStart()681     void onReturnTransitionStart() {
682         if (mDetailsBackgroundController != null) {
683             // first disable parallax effect that auto-start PlaybackGlue.
684             boolean isVideoVisible = mDetailsBackgroundController.disableVideoParallax();
685             // if video is not visible we can safely remove VideoSupportFragment,
686             // otherwise let video playing during return transition.
687             if (!isVideoVisible && mVideoSupportFragment != null) {
688                 FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
689                 ft2.remove(mVideoSupportFragment);
690                 ft2.commit();
691                 mVideoSupportFragment = null;
692             }
693         }
694     }
695 
696     @Override
onStop()697     public void onStop() {
698         if (mDetailsBackgroundController != null) {
699             mDetailsBackgroundController.onStop();
700         }
701         super.onStop();
702     }
703 
704     /**
705      * Called on every visible row to change view status when current selected row position
706      * or selected sub position changed.  Subclass may override.   The default
707      * implementation calls {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter,
708      * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)} if presenter is
709      * instance of {@link FullWidthDetailsOverviewRowPresenter}.
710      *
711      * @param presenter   The presenter used to create row ViewHolder.
712      * @param viewHolder  The visible (attached) row ViewHolder, note that it may or may not
713      *                    be selected.
714      * @param adapterPosition  The adapter position of viewHolder inside adapter.
715      * @param selectedPosition The adapter position of currently selected row.
716      * @param selectedSubPosition The sub position within currently selected row.  This is used
717      *                            When a row has multiple alignment positions.
718      */
onSetRowStatus(RowPresenter presenter, RowPresenter.ViewHolder viewHolder, int adapterPosition, int selectedPosition, int selectedSubPosition)719     protected void onSetRowStatus(RowPresenter presenter, RowPresenter.ViewHolder viewHolder, int
720             adapterPosition, int selectedPosition, int selectedSubPosition) {
721         if (presenter instanceof FullWidthDetailsOverviewRowPresenter) {
722             onSetDetailsOverviewRowStatus((FullWidthDetailsOverviewRowPresenter) presenter,
723                     (FullWidthDetailsOverviewRowPresenter.ViewHolder) viewHolder,
724                     adapterPosition, selectedPosition, selectedSubPosition);
725         }
726     }
727 
728     /**
729      * Called to change DetailsOverviewRow view status when current selected row position
730      * or selected sub position changed.  Subclass may override.   The default
731      * implementation switches between three states based on the positions:
732      * {@link FullWidthDetailsOverviewRowPresenter#STATE_HALF},
733      * {@link FullWidthDetailsOverviewRowPresenter#STATE_FULL} and
734      * {@link FullWidthDetailsOverviewRowPresenter#STATE_SMALL}.
735      *
736      * @param presenter   The presenter used to create row ViewHolder.
737      * @param viewHolder  The visible (attached) row ViewHolder, note that it may or may not
738      *                    be selected.
739      * @param adapterPosition  The adapter position of viewHolder inside adapter.
740      * @param selectedPosition The adapter position of currently selected row.
741      * @param selectedSubPosition The sub position within currently selected row.  This is used
742      *                            When a row has multiple alignment positions.
743      */
onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter presenter, FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder, int adapterPosition, int selectedPosition, int selectedSubPosition)744     protected void onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter presenter,
745             FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder, int adapterPosition,
746             int selectedPosition, int selectedSubPosition) {
747         if (selectedPosition > adapterPosition) {
748             presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF);
749         } else if (selectedPosition == adapterPosition && selectedSubPosition == 1) {
750             presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF);
751         } else if (selectedPosition == adapterPosition && selectedSubPosition == 0){
752             presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_FULL);
753         } else {
754             presenter.setState(viewHolder,
755                     FullWidthDetailsOverviewRowPresenter.STATE_SMALL);
756         }
757     }
758 
759     @Override
onStart()760     public void onStart() {
761         super.onStart();
762 
763         setupChildFragmentLayout();
764         mStateMachine.fireEvent(EVT_ONSTART);
765         if (mDetailsParallax != null) {
766             mDetailsParallax.setRecyclerView(mRowsSupportFragment.getVerticalGridView());
767         }
768         if (mPendingFocusOnVideo) {
769             slideOutGridView();
770         } else if (!getView().hasFocus()) {
771             mRowsSupportFragment.getVerticalGridView().requestFocus();
772         }
773     }
774 
775     @Override
createEntranceTransition()776     protected Object createEntranceTransition() {
777         return TransitionHelper.loadTransition(getContext(),
778                 R.transition.lb_details_enter_transition);
779     }
780 
781     @Override
runEntranceTransition(Object entranceTransition)782     protected void runEntranceTransition(Object entranceTransition) {
783         TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
784     }
785 
786     @Override
onEntranceTransitionEnd()787     protected void onEntranceTransitionEnd() {
788         mRowsSupportFragment.onTransitionEnd();
789     }
790 
791     @Override
onEntranceTransitionPrepare()792     protected void onEntranceTransitionPrepare() {
793         mRowsSupportFragment.onTransitionPrepare();
794     }
795 
796     @Override
onEntranceTransitionStart()797     protected void onEntranceTransitionStart() {
798         mRowsSupportFragment.onTransitionStart();
799     }
800 
801     /**
802      * Returns the {@link DetailsParallax} instance used by
803      * {@link DetailsSupportFragmentBackgroundController} to configure parallax effect of background and
804      * control embedded video playback. App usually does not use this method directly.
805      * App may use this method for other custom parallax tasks.
806      *
807      * @return The DetailsParallax instance attached to the DetailsSupportFragment.
808      */
getParallax()809     public DetailsParallax getParallax() {
810         if (mDetailsParallax == null) {
811             mDetailsParallax = new DetailsParallax();
812             if (mRowsSupportFragment != null && mRowsSupportFragment.getView() != null) {
813                 mDetailsParallax.setRecyclerView(mRowsSupportFragment.getVerticalGridView());
814             }
815         }
816         return mDetailsParallax;
817     }
818 
819     /**
820      * Set background drawable shown below foreground rows UI and above
821      * {@link #findOrCreateVideoSupportFragment()}.
822      *
823      * @see DetailsSupportFragmentBackgroundController
824      */
setBackgroundDrawable(Drawable drawable)825     void setBackgroundDrawable(Drawable drawable) {
826         if (mBackgroundView != null) {
827             mBackgroundView.setBackground(drawable);
828         }
829         mBackgroundDrawable = drawable;
830     }
831 
832     /**
833      * This method does the following
834      * <ul>
835      * <li>sets up focus search handling logic in the root view to enable transitioning between
836      * half screen/full screen/no video mode.</li>
837      *
838      * <li>Sets up the key listener in the root view to intercept events like UP/DOWN and
839      * transition to appropriate mode like half/full screen video.</li>
840      * </ul>
841      */
setupDpadNavigation()842     void setupDpadNavigation() {
843         mRootView.setOnChildFocusListener(new BrowseFrameLayout.OnChildFocusListener() {
844 
845             @Override
846             public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
847                 return false;
848             }
849 
850             @Override
851             public void onRequestChildFocus(View child, View focused) {
852                 if (child != mRootView.getFocusedChild()) {
853                     if (child.getId() == R.id.details_fragment_root) {
854                         if (!mPendingFocusOnVideo) {
855                             slideInGridView();
856                             showTitle(true);
857                         }
858                     } else if (child.getId() == R.id.video_surface_container) {
859                         slideOutGridView();
860                         showTitle(false);
861                     } else {
862                         showTitle(true);
863                     }
864                 }
865             }
866         });
867         mRootView.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() {
868             @Override
869             public View onFocusSearch(View focused, int direction) {
870                 if (mRowsSupportFragment.getVerticalGridView() != null
871                         && mRowsSupportFragment.getVerticalGridView().hasFocus()) {
872                     if (direction == View.FOCUS_UP) {
873                         if (mDetailsBackgroundController != null
874                                 && mDetailsBackgroundController.canNavigateToVideoSupportFragment()
875                                 && mVideoSupportFragment != null && mVideoSupportFragment.getView() != null) {
876                             return mVideoSupportFragment.getView();
877                         } else if (getTitleView() != null && getTitleView().hasFocusable()) {
878                             return getTitleView();
879                         }
880                     }
881                 } else if (getTitleView() != null && getTitleView().hasFocus()) {
882                     if (direction == View.FOCUS_DOWN) {
883                         if (mRowsSupportFragment.getVerticalGridView() != null) {
884                             return mRowsSupportFragment.getVerticalGridView();
885                         }
886                     }
887                 }
888                 return focused;
889             }
890         });
891 
892         // If we press BACK on remote while in full screen video mode, we should
893         // transition back to half screen video playback mode.
894         mRootView.setOnDispatchKeyListener(new View.OnKeyListener() {
895             @Override
896             public boolean onKey(View v, int keyCode, KeyEvent event) {
897                 // This is used to check if we are in full screen video mode. This is somewhat
898                 // hacky and relies on the behavior of the video helper class to update the
899                 // focusability of the video surface view.
900                 if (mVideoSupportFragment != null && mVideoSupportFragment.getView() != null
901                         && mVideoSupportFragment.getView().hasFocus()) {
902                     if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
903                         if (getVerticalGridView().getChildCount() > 0) {
904                             getVerticalGridView().requestFocus();
905                             return true;
906                         }
907                     }
908                 }
909 
910                 return false;
911             }
912         });
913     }
914 
915     /**
916      * Slides vertical grid view (displaying media item details) out of the screen from below.
917      */
slideOutGridView()918     void slideOutGridView() {
919         if (getVerticalGridView() != null) {
920             getVerticalGridView().animateOut();
921         }
922     }
923 
slideInGridView()924     void slideInGridView() {
925         if (getVerticalGridView() != null) {
926             getVerticalGridView().animateIn();
927         }
928     }
929 }
930