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