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