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 */
20package androidx.leanback.app;
21
22import android.app.Activity;
23import android.app.Fragment;
24import android.app.FragmentTransaction;
25import android.graphics.Rect;
26import android.graphics.drawable.Drawable;
27import android.os.Build;
28import android.os.Bundle;
29import android.util.Log;
30import android.view.KeyEvent;
31import android.view.LayoutInflater;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.Window;
35
36import androidx.annotation.CallSuper;
37import androidx.leanback.R;
38import androidx.leanback.transition.TransitionHelper;
39import androidx.leanback.transition.TransitionListener;
40import androidx.leanback.util.StateMachine.Event;
41import androidx.leanback.util.StateMachine.State;
42import androidx.leanback.widget.BaseOnItemViewClickedListener;
43import androidx.leanback.widget.BaseOnItemViewSelectedListener;
44import androidx.leanback.widget.BrowseFrameLayout;
45import androidx.leanback.widget.DetailsParallax;
46import androidx.leanback.widget.FullWidthDetailsOverviewRowPresenter;
47import androidx.leanback.widget.ItemAlignmentFacet;
48import androidx.leanback.widget.ItemBridgeAdapter;
49import androidx.leanback.widget.ObjectAdapter;
50import androidx.leanback.widget.Presenter;
51import androidx.leanback.widget.PresenterSelector;
52import androidx.leanback.widget.RowPresenter;
53import androidx.leanback.widget.VerticalGridView;
54
55import 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
98public 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
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
179        WaitEnterTransitionTimeout(DetailsFragment f) {
180            mRef = new WeakReference<>(f);
181            f.getView().postDelayed(this, WAIT_ENTERTRANSITION_START);
182        }
183
184        @Override
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
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
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
291        SetSelectionRunnable() {
292        }
293
294        @Override
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     */
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     */
391    public ObjectAdapter getAdapter() {
392        return mAdapter;
393    }
394
395    /**
396     * Sets an item selection listener.
397     */
398    public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
399        mExternalOnItemViewSelectedListener = listener;
400    }
401
402    /**
403     * Sets an item clicked listener.
404     */
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     */
417    public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
418        return mOnItemViewClickedListener;
419    }
420
421    @Override
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
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
495    protected View inflateTitle(LayoutInflater inflater, ViewGroup parent,
496            Bundle savedInstanceState) {
497        return super.onInflateTitleView(inflater, parent, savedInstanceState);
498    }
499
500    @Override
501    public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
502                                   Bundle savedInstanceState) {
503        return inflateTitle(inflater, parent, savedInstanceState);
504    }
505
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     */
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     */
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
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     */
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     */
571    private void setupChildFragmentLayout() {
572        setVerticalGridViewLayout(mRowsFragment.getVerticalGridView());
573    }
574
575    /**
576     * Sets the selected row position with smooth animation.
577     */
578    public void setSelectedPosition(int position) {
579        setSelectedPosition(position, true);
580    }
581
582    /**
583     * Sets the selected row position.
584     */
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
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
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     */
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
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
680    void onSafeStart() {
681        if (mDetailsBackgroundController != null) {
682            mDetailsBackgroundController.onStart();
683        }
684    }
685
686    @CallSuper
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
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     */
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     */
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
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
782    protected Object createEntranceTransition() {
783        return TransitionHelper.loadTransition(FragmentUtil.getContext(DetailsFragment.this),
784                R.transition.lb_details_enter_transition);
785    }
786
787    @Override
788    protected void runEntranceTransition(Object entranceTransition) {
789        TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
790    }
791
792    @Override
793    protected void onEntranceTransitionEnd() {
794        mRowsFragment.onTransitionEnd();
795    }
796
797    @Override
798    protected void onEntranceTransitionPrepare() {
799        mRowsFragment.onTransitionPrepare();
800    }
801
802    @Override
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     */
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     */
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     */
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     */
924    void slideOutGridView() {
925        if (getVerticalGridView() != null) {
926            getVerticalGridView().animateOut();
927        }
928    }
929
930    void slideInGridView() {
931        if (getVerticalGridView() != null) {
932            getVerticalGridView().animateIn();
933        }
934    }
935}
936