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