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 */
17package androidx.leanback.app;
18
19import android.graphics.Rect;
20import android.graphics.drawable.Drawable;
21import android.os.Build;
22import android.os.Bundle;
23import android.util.Log;
24import android.view.KeyEvent;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.view.ViewGroup;
28import android.view.Window;
29
30import androidx.annotation.CallSuper;
31import androidx.fragment.app.Fragment;
32import androidx.fragment.app.FragmentActivity;
33import androidx.fragment.app.FragmentTransaction;
34import androidx.leanback.R;
35import androidx.leanback.transition.TransitionHelper;
36import androidx.leanback.transition.TransitionListener;
37import androidx.leanback.util.StateMachine.Event;
38import androidx.leanback.util.StateMachine.State;
39import androidx.leanback.widget.BaseOnItemViewClickedListener;
40import androidx.leanback.widget.BaseOnItemViewSelectedListener;
41import androidx.leanback.widget.BrowseFrameLayout;
42import androidx.leanback.widget.DetailsParallax;
43import androidx.leanback.widget.FullWidthDetailsOverviewRowPresenter;
44import androidx.leanback.widget.ItemAlignmentFacet;
45import androidx.leanback.widget.ItemBridgeAdapter;
46import androidx.leanback.widget.ObjectAdapter;
47import androidx.leanback.widget.Presenter;
48import androidx.leanback.widget.PresenterSelector;
49import androidx.leanback.widget.RowPresenter;
50import androidx.leanback.widget.VerticalGridView;
51
52import 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 */
93public 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
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
174        WaitEnterTransitionTimeout(DetailsSupportFragment f) {
175            mRef = new WeakReference<>(f);
176            f.getView().postDelayed(this, WAIT_ENTERTRANSITION_START);
177        }
178
179        @Override
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
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
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
286        SetSelectionRunnable() {
287        }
288
289        @Override
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     */
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     */
386    public ObjectAdapter getAdapter() {
387        return mAdapter;
388    }
389
390    /**
391     * Sets an item selection listener.
392     */
393    public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
394        mExternalOnItemViewSelectedListener = listener;
395    }
396
397    /**
398     * Sets an item clicked listener.
399     */
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     */
412    public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
413        return mOnItemViewClickedListener;
414    }
415
416    @Override
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
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
490    protected View inflateTitle(LayoutInflater inflater, ViewGroup parent,
491            Bundle savedInstanceState) {
492        return super.onInflateTitleView(inflater, parent, savedInstanceState);
493    }
494
495    @Override
496    public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
497                                   Bundle savedInstanceState) {
498        return inflateTitle(inflater, parent, savedInstanceState);
499    }
500
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     */
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     */
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
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     */
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     */
566    private void setupChildFragmentLayout() {
567        setVerticalGridViewLayout(mRowsSupportFragment.getVerticalGridView());
568    }
569
570    /**
571     * Sets the selected row position with smooth animation.
572     */
573    public void setSelectedPosition(int position) {
574        setSelectedPosition(position, true);
575    }
576
577    /**
578     * Sets the selected row position.
579     */
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
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
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     */
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
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
675    void onSafeStart() {
676        if (mDetailsBackgroundController != null) {
677            mDetailsBackgroundController.onStart();
678        }
679    }
680
681    @CallSuper
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
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     */
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     */
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
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
777    protected Object createEntranceTransition() {
778        return TransitionHelper.loadTransition(getContext(),
779                R.transition.lb_details_enter_transition);
780    }
781
782    @Override
783    protected void runEntranceTransition(Object entranceTransition) {
784        TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
785    }
786
787    @Override
788    protected void onEntranceTransitionEnd() {
789        mRowsSupportFragment.onTransitionEnd();
790    }
791
792    @Override
793    protected void onEntranceTransitionPrepare() {
794        mRowsSupportFragment.onTransitionPrepare();
795    }
796
797    @Override
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     */
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     */
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     */
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     */
919    void slideOutGridView() {
920        if (getVerticalGridView() != null) {
921            getVerticalGridView().animateOut();
922        }
923    }
924
925    void slideInGridView() {
926        if (getVerticalGridView() != null) {
927            getVerticalGridView().animateIn();
928        }
929    }
930}
931