Home | History | Annotate | Download | only in app
      1 // CHECKSTYLE:OFF Generated code
      2 /* This file is auto-generated from PlaybackSupportFragment.java.  DO NOT MODIFY. */
      3 
      4 /*
      5  * Copyright (C) 2016 The Android Open Source Project
      6  *
      7  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      8  * in compliance with the License. You may obtain a copy of the License at
      9  *
     10  * http://www.apache.org/licenses/LICENSE-2.0
     11  *
     12  * Unless required by applicable law or agreed to in writing, software distributed under the License
     13  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     14  * or implied. See the License for the specific language governing permissions and limitations under
     15  * the License.
     16  */
     17 package androidx.leanback.app;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorInflater;
     21 import android.animation.TimeInterpolator;
     22 import android.animation.ValueAnimator;
     23 import android.animation.ValueAnimator.AnimatorUpdateListener;
     24 import android.app.Fragment;
     25 import android.content.Context;
     26 import android.graphics.Color;
     27 import android.graphics.drawable.ColorDrawable;
     28 import android.os.Bundle;
     29 import android.os.Handler;
     30 import android.os.Message;
     31 import android.util.Log;
     32 import android.view.InputEvent;
     33 import android.view.KeyEvent;
     34 import android.view.LayoutInflater;
     35 import android.view.MotionEvent;
     36 import android.view.View;
     37 import android.view.ViewGroup;
     38 import android.view.animation.AccelerateInterpolator;
     39 
     40 import androidx.annotation.NonNull;
     41 import androidx.annotation.Nullable;
     42 import androidx.annotation.RestrictTo;
     43 import androidx.leanback.R;
     44 import androidx.leanback.animation.LogAccelerateInterpolator;
     45 import androidx.leanback.animation.LogDecelerateInterpolator;
     46 import androidx.leanback.media.PlaybackGlueHost;
     47 import androidx.leanback.widget.ArrayObjectAdapter;
     48 import androidx.leanback.widget.BaseOnItemViewClickedListener;
     49 import androidx.leanback.widget.BaseOnItemViewSelectedListener;
     50 import androidx.leanback.widget.ClassPresenterSelector;
     51 import androidx.leanback.widget.ItemAlignmentFacet;
     52 import androidx.leanback.widget.ItemBridgeAdapter;
     53 import androidx.leanback.widget.ObjectAdapter;
     54 import androidx.leanback.widget.PlaybackRowPresenter;
     55 import androidx.leanback.widget.PlaybackSeekDataProvider;
     56 import androidx.leanback.widget.PlaybackSeekUi;
     57 import androidx.leanback.widget.Presenter;
     58 import androidx.leanback.widget.PresenterSelector;
     59 import androidx.leanback.widget.Row;
     60 import androidx.leanback.widget.RowPresenter;
     61 import androidx.leanback.widget.SparseArrayObjectAdapter;
     62 import androidx.leanback.widget.VerticalGridView;
     63 import androidx.recyclerview.widget.RecyclerView;
     64 
     65 /**
     66  * A fragment for displaying playback controls and related content.
     67  *
     68  * <p>
     69  * A PlaybackFragment renders the elements of its {@link ObjectAdapter} as a set
     70  * of rows in a vertical list.  The Adapter's {@link PresenterSelector} must maintain subclasses
     71  * of {@link RowPresenter}.
     72  * </p>
     73  * <p>
     74  * A playback row is a row rendered by {@link PlaybackRowPresenter}.
     75  * App can call {@link #setPlaybackRow(Row)} to set playback row for the first element of adapter.
     76  * App can call {@link #setPlaybackRowPresenter(PlaybackRowPresenter)} to set presenter for it.
     77  * {@link #setPlaybackRow(Row)} and {@link #setPlaybackRowPresenter(PlaybackRowPresenter)} are
     78  * optional, app can pass playback row and PlaybackRowPresenter in the adapter using
     79  * {@link #setAdapter(ObjectAdapter)}.
     80  * </p>
     81  * <p>
     82  * Auto hide controls upon playing: best practice is calling
     83  * {@link #setControlsOverlayAutoHideEnabled(boolean)} upon play/pause. The auto hiding timer will
     84  * be cancelled upon {@link #tickle()} triggered by input event.
     85  * </p>
     86  * @deprecated use {@link PlaybackSupportFragment}
     87  */
     88 @Deprecated
     89 public class PlaybackFragment extends Fragment {
     90     static final String BUNDLE_CONTROL_VISIBLE_ON_CREATEVIEW = "controlvisible_oncreateview";
     91 
     92     /**
     93      * No background.
     94      */
     95     public static final int BG_NONE = 0;
     96 
     97     /**
     98      * A dark translucent background.
     99      */
    100     public static final int BG_DARK = 1;
    101     PlaybackGlueHost.HostCallback mHostCallback;
    102 
    103     PlaybackSeekUi.Client mSeekUiClient;
    104     boolean mInSeek;
    105     ProgressBarManager mProgressBarManager = new ProgressBarManager();
    106 
    107     /**
    108      * Resets the focus on the button in the middle of control row.
    109      * @hide
    110      */
    111     @RestrictTo(RestrictTo.Scope.LIBRARY)
    112     public void resetFocus() {
    113         ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView()
    114                 .findViewHolderForAdapterPosition(0);
    115         if (vh != null && vh.getPresenter() instanceof PlaybackRowPresenter) {
    116             ((PlaybackRowPresenter) vh.getPresenter()).onReappear(
    117                     (RowPresenter.ViewHolder) vh.getViewHolder());
    118         }
    119     }
    120 
    121     private class SetSelectionRunnable implements Runnable {
    122         int mPosition;
    123         boolean mSmooth = true;
    124 
    125         @Override
    126         public void run() {
    127             if (mRowsFragment == null) {
    128                 return;
    129             }
    130             mRowsFragment.setSelectedPosition(mPosition, mSmooth);
    131         }
    132     }
    133 
    134     /**
    135      * A light translucent background.
    136      */
    137     public static final int BG_LIGHT = 2;
    138     RowsFragment mRowsFragment;
    139     ObjectAdapter mAdapter;
    140     PlaybackRowPresenter mPresenter;
    141     Row mRow;
    142     BaseOnItemViewSelectedListener mExternalItemSelectedListener;
    143     BaseOnItemViewClickedListener mExternalItemClickedListener;
    144     BaseOnItemViewClickedListener mPlaybackItemClickedListener;
    145 
    146     private final BaseOnItemViewClickedListener mOnItemViewClickedListener =
    147             new BaseOnItemViewClickedListener() {
    148                 @Override
    149                 public void onItemClicked(Presenter.ViewHolder itemViewHolder,
    150                                           Object item,
    151                                           RowPresenter.ViewHolder rowViewHolder,
    152                                           Object row) {
    153                     if (mPlaybackItemClickedListener != null
    154                             && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder) {
    155                         mPlaybackItemClickedListener.onItemClicked(
    156                                 itemViewHolder, item, rowViewHolder, row);
    157                     }
    158                     if (mExternalItemClickedListener != null) {
    159                         mExternalItemClickedListener.onItemClicked(
    160                                 itemViewHolder, item, rowViewHolder, row);
    161                     }
    162                 }
    163             };
    164 
    165     private final BaseOnItemViewSelectedListener mOnItemViewSelectedListener =
    166             new BaseOnItemViewSelectedListener() {
    167                 @Override
    168                 public void onItemSelected(Presenter.ViewHolder itemViewHolder,
    169                                            Object item,
    170                                            RowPresenter.ViewHolder rowViewHolder,
    171                                            Object row) {
    172                     if (mExternalItemSelectedListener != null) {
    173                         mExternalItemSelectedListener.onItemSelected(
    174                                 itemViewHolder, item, rowViewHolder, row);
    175                     }
    176                 }
    177             };
    178 
    179     private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
    180 
    181     public ObjectAdapter getAdapter() {
    182         return mAdapter;
    183     }
    184 
    185     /**
    186      * Listener allowing the application to receive notification of fade in and/or fade out
    187      * completion events.
    188      * @hide
    189      * @deprecated use {@link PlaybackSupportFragment}
    190      */
    191     @RestrictTo(RestrictTo.Scope.LIBRARY)
    192     @Deprecated
    193     public static class OnFadeCompleteListener {
    194         public void onFadeInComplete() {
    195         }
    196 
    197         public void onFadeOutComplete() {
    198         }
    199     }
    200 
    201     private static final String TAG = "PlaybackFragment";
    202     private static final boolean DEBUG = false;
    203     private static final int ANIMATION_MULTIPLIER = 1;
    204 
    205     private static final int START_FADE_OUT = 1;
    206 
    207     // Fading status
    208     private static final int IDLE = 0;
    209     private static final int ANIMATING = 1;
    210 
    211     int mPaddingBottom;
    212     int mOtherRowsCenterToBottom;
    213     View mRootView;
    214     View mBackgroundView;
    215     int mBackgroundType = BG_DARK;
    216     int mBgDarkColor;
    217     int mBgLightColor;
    218     int mShowTimeMs;
    219     int mMajorFadeTranslateY, mMinorFadeTranslateY;
    220     int mAnimationTranslateY;
    221     OnFadeCompleteListener mFadeCompleteListener;
    222     View.OnKeyListener mInputEventHandler;
    223     boolean mFadingEnabled = true;
    224     boolean mControlVisibleBeforeOnCreateView = true;
    225     boolean mControlVisible = true;
    226     int mBgAlpha;
    227     ValueAnimator mBgFadeInAnimator, mBgFadeOutAnimator;
    228     ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator;
    229     ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
    230 
    231     private final Animator.AnimatorListener mFadeListener =
    232             new Animator.AnimatorListener() {
    233                 @Override
    234                 public void onAnimationStart(Animator animation) {
    235                     enableVerticalGridAnimations(false);
    236                 }
    237 
    238                 @Override
    239                 public void onAnimationRepeat(Animator animation) {
    240                 }
    241 
    242                 @Override
    243                 public void onAnimationCancel(Animator animation) {
    244                 }
    245 
    246                 @Override
    247                 public void onAnimationEnd(Animator animation) {
    248                     if (DEBUG) Log.v(TAG, "onAnimationEnd " + mBgAlpha);
    249                     if (mBgAlpha > 0) {
    250                         enableVerticalGridAnimations(true);
    251                         if (mFadeCompleteListener != null) {
    252                             mFadeCompleteListener.onFadeInComplete();
    253                         }
    254                     } else {
    255                         VerticalGridView verticalView = getVerticalGridView();
    256                         // reset focus to the primary actions only if the selected row was the controls row
    257                         if (verticalView != null && verticalView.getSelectedPosition() == 0) {
    258                             ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
    259                                     verticalView.findViewHolderForAdapterPosition(0);
    260                             if (vh != null && vh.getPresenter() instanceof PlaybackRowPresenter) {
    261                                 ((PlaybackRowPresenter)vh.getPresenter()).onReappear(
    262                                         (RowPresenter.ViewHolder) vh.getViewHolder());
    263                             }
    264                         }
    265                         if (mFadeCompleteListener != null) {
    266                             mFadeCompleteListener.onFadeOutComplete();
    267                         }
    268                     }
    269                 }
    270             };
    271 
    272     public PlaybackFragment() {
    273         mProgressBarManager.setInitialDelay(500);
    274     }
    275 
    276     VerticalGridView getVerticalGridView() {
    277         if (mRowsFragment == null) {
    278             return null;
    279         }
    280         return mRowsFragment.getVerticalGridView();
    281     }
    282 
    283     private final Handler mHandler = new Handler() {
    284         @Override
    285         public void handleMessage(Message message) {
    286             if (message.what == START_FADE_OUT && mFadingEnabled) {
    287                 hideControlsOverlay(true);
    288             }
    289         }
    290     };
    291 
    292     private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
    293             new VerticalGridView.OnTouchInterceptListener() {
    294                 @Override
    295                 public boolean onInterceptTouchEvent(MotionEvent event) {
    296                     return onInterceptInputEvent(event);
    297                 }
    298             };
    299 
    300     private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
    301             new VerticalGridView.OnKeyInterceptListener() {
    302                 @Override
    303                 public boolean onInterceptKeyEvent(KeyEvent event) {
    304                     return onInterceptInputEvent(event);
    305                 }
    306             };
    307 
    308     private void setBgAlpha(int alpha) {
    309         mBgAlpha = alpha;
    310         if (mBackgroundView != null) {
    311             mBackgroundView.getBackground().setAlpha(alpha);
    312         }
    313     }
    314 
    315     private void enableVerticalGridAnimations(boolean enable) {
    316         if (getVerticalGridView() != null) {
    317             getVerticalGridView().setAnimateChildLayout(enable);
    318         }
    319     }
    320 
    321     /**
    322      * Enables or disables auto hiding controls overlay after a short delay fragment is resumed.
    323      * If enabled and fragment is resumed, the view will fade out after a time period.
    324      * {@link #tickle()} will kill the timer, next time fragment is resumed,
    325      * the timer will be started again if {@link #isControlsOverlayAutoHideEnabled()} is true.
    326      */
    327     public void setControlsOverlayAutoHideEnabled(boolean enabled) {
    328         if (DEBUG) Log.v(TAG, "setControlsOverlayAutoHideEnabled " + enabled);
    329         if (enabled != mFadingEnabled) {
    330             mFadingEnabled = enabled;
    331             if (isResumed() && getView().hasFocus()) {
    332                 showControlsOverlay(true);
    333                 if (enabled) {
    334                     // StateGraph 7->2 5->2
    335                     startFadeTimer();
    336                 } else {
    337                     // StateGraph 4->5 2->5
    338                     stopFadeTimer();
    339                 }
    340             } else {
    341                 // StateGraph 6->1 1->6
    342             }
    343         }
    344     }
    345 
    346     /**
    347      * Returns true if controls will be auto hidden after a delay when fragment is resumed.
    348      */
    349     public boolean isControlsOverlayAutoHideEnabled() {
    350         return mFadingEnabled;
    351     }
    352 
    353     /**
    354      * @deprecated Uses {@link #setControlsOverlayAutoHideEnabled(boolean)}
    355      */
    356     @Deprecated
    357     public void setFadingEnabled(boolean enabled) {
    358         setControlsOverlayAutoHideEnabled(enabled);
    359     }
    360 
    361     /**
    362      * @deprecated Uses {@link #isControlsOverlayAutoHideEnabled()}
    363      */
    364     @Deprecated
    365     public boolean isFadingEnabled() {
    366         return isControlsOverlayAutoHideEnabled();
    367     }
    368 
    369     /**
    370      * Sets the listener to be called when fade in or out has completed.
    371      * @hide
    372      */
    373     @RestrictTo(RestrictTo.Scope.LIBRARY)
    374     public void setFadeCompleteListener(OnFadeCompleteListener listener) {
    375         mFadeCompleteListener = listener;
    376     }
    377 
    378     /**
    379      * Returns the listener to be called when fade in or out has completed.
    380      * @hide
    381      */
    382     @RestrictTo(RestrictTo.Scope.LIBRARY)
    383     public OnFadeCompleteListener getFadeCompleteListener() {
    384         return mFadeCompleteListener;
    385     }
    386 
    387     /**
    388      * Sets the input event handler.
    389      */
    390     public final void setOnKeyInterceptListener(View.OnKeyListener handler) {
    391         mInputEventHandler = handler;
    392     }
    393 
    394     /**
    395      * Tickles the playback controls. Fades in the view if it was faded out. {@link #tickle()} will
    396      * also kill the timer created by {@link #setControlsOverlayAutoHideEnabled(boolean)}. When
    397      * next time fragment is resumed, the timer will be started again if
    398      * {@link #isControlsOverlayAutoHideEnabled()} is true. In most cases app does not need call
    399      * this method, tickling on input events is handled by the fragment.
    400      */
    401     public void tickle() {
    402         if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
    403         //StateGraph 2->4
    404         stopFadeTimer();
    405         showControlsOverlay(true);
    406     }
    407 
    408     private boolean onInterceptInputEvent(InputEvent event) {
    409         final boolean controlsHidden = !mControlVisible;
    410         if (DEBUG) Log.v(TAG, "onInterceptInputEvent hidden " + controlsHidden + " " + event);
    411         boolean consumeEvent = false;
    412         int keyCode = KeyEvent.KEYCODE_UNKNOWN;
    413         int keyAction = 0;
    414 
    415         if (event instanceof KeyEvent) {
    416             keyCode = ((KeyEvent) event).getKeyCode();
    417             keyAction = ((KeyEvent) event).getAction();
    418             if (mInputEventHandler != null) {
    419                 consumeEvent = mInputEventHandler.onKey(getView(), keyCode, (KeyEvent) event);
    420             }
    421         }
    422 
    423         switch (keyCode) {
    424             case KeyEvent.KEYCODE_DPAD_CENTER:
    425             case KeyEvent.KEYCODE_DPAD_DOWN:
    426             case KeyEvent.KEYCODE_DPAD_UP:
    427             case KeyEvent.KEYCODE_DPAD_LEFT:
    428             case KeyEvent.KEYCODE_DPAD_RIGHT:
    429                 // Event may be consumed; regardless, if controls are hidden then these keys will
    430                 // bring up the controls.
    431                 if (controlsHidden) {
    432                     consumeEvent = true;
    433                 }
    434                 if (keyAction == KeyEvent.ACTION_DOWN) {
    435                     tickle();
    436                 }
    437                 break;
    438             case KeyEvent.KEYCODE_BACK:
    439             case KeyEvent.KEYCODE_ESCAPE:
    440                 if (mInSeek) {
    441                     // when in seek, the SeekUi will handle the BACK.
    442                     return false;
    443                 }
    444                 // If controls are not hidden, back will be consumed to fade
    445                 // them out (even if the key was consumed by the handler).
    446                 if (!controlsHidden) {
    447                     consumeEvent = true;
    448 
    449                     if (((KeyEvent) event).getAction() == KeyEvent.ACTION_UP) {
    450                         hideControlsOverlay(true);
    451                     }
    452                 }
    453                 break;
    454             default:
    455                 if (consumeEvent) {
    456                     if (keyAction == KeyEvent.ACTION_DOWN) {
    457                         tickle();
    458                     }
    459                 }
    460         }
    461         return consumeEvent;
    462     }
    463 
    464     @Override
    465     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    466         super.onViewCreated(view, savedInstanceState);
    467         // controls view are initially visible, make it invisible
    468         // if app has called hideControlsOverlay() before view created.
    469         mControlVisible = true;
    470         if (!mControlVisibleBeforeOnCreateView) {
    471             showControlsOverlay(false, false);
    472             mControlVisibleBeforeOnCreateView = true;
    473         }
    474     }
    475 
    476     @Override
    477     public void onResume() {
    478         super.onResume();
    479 
    480         if (mControlVisible) {
    481             //StateGraph: 6->5 1->2
    482             if (mFadingEnabled) {
    483                 // StateGraph 1->2
    484                 startFadeTimer();
    485             }
    486         } else {
    487             //StateGraph: 6->7 1->3
    488         }
    489         getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
    490         getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
    491         if (mHostCallback != null) {
    492             mHostCallback.onHostResume();
    493         }
    494     }
    495 
    496     private void stopFadeTimer() {
    497         if (mHandler != null) {
    498             mHandler.removeMessages(START_FADE_OUT);
    499         }
    500     }
    501 
    502     private void startFadeTimer() {
    503         if (mHandler != null) {
    504             mHandler.removeMessages(START_FADE_OUT);
    505             mHandler.sendEmptyMessageDelayed(START_FADE_OUT, mShowTimeMs);
    506         }
    507     }
    508 
    509     private static ValueAnimator loadAnimator(Context context, int resId) {
    510         ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
    511         animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
    512         return animator;
    513     }
    514 
    515     private void loadBgAnimator() {
    516         AnimatorUpdateListener listener = new AnimatorUpdateListener() {
    517             @Override
    518             public void onAnimationUpdate(ValueAnimator arg0) {
    519                 setBgAlpha((Integer) arg0.getAnimatedValue());
    520             }
    521         };
    522 
    523         Context context = FragmentUtil.getContext(PlaybackFragment.this);
    524         mBgFadeInAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_in);
    525         mBgFadeInAnimator.addUpdateListener(listener);
    526         mBgFadeInAnimator.addListener(mFadeListener);
    527 
    528         mBgFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_out);
    529         mBgFadeOutAnimator.addUpdateListener(listener);
    530         mBgFadeOutAnimator.addListener(mFadeListener);
    531     }
    532 
    533     private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100, 0);
    534     private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100, 0);
    535 
    536     private void loadControlRowAnimator() {
    537         final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
    538             @Override
    539             public void onAnimationUpdate(ValueAnimator arg0) {
    540                 if (getVerticalGridView() == null) {
    541                     return;
    542                 }
    543                 RecyclerView.ViewHolder vh = getVerticalGridView()
    544                         .findViewHolderForAdapterPosition(0);
    545                 if (vh == null) {
    546                     return;
    547                 }
    548                 View view = vh.itemView;
    549                 if (view != null) {
    550                     final float fraction = (Float) arg0.getAnimatedValue();
    551                     if (DEBUG) Log.v(TAG, "fraction " + fraction);
    552                     view.setAlpha(fraction);
    553                     view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
    554                 }
    555             }
    556         };
    557 
    558         Context context = FragmentUtil.getContext(PlaybackFragment.this);
    559         mControlRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
    560         mControlRowFadeInAnimator.addUpdateListener(updateListener);
    561         mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
    562 
    563         mControlRowFadeOutAnimator = loadAnimator(context,
    564                 R.animator.lb_playback_controls_fade_out);
    565         mControlRowFadeOutAnimator.addUpdateListener(updateListener);
    566         mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
    567     }
    568 
    569     private void loadOtherRowAnimator() {
    570         final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
    571             @Override
    572             public void onAnimationUpdate(ValueAnimator arg0) {
    573                 if (getVerticalGridView() == null) {
    574                     return;
    575                 }
    576                 final float fraction = (Float) arg0.getAnimatedValue();
    577                 final int count = getVerticalGridView().getChildCount();
    578                 for (int i = 0; i < count; i++) {
    579                     View view = getVerticalGridView().getChildAt(i);
    580                     if (getVerticalGridView().getChildAdapterPosition(view) > 0) {
    581                         view.setAlpha(fraction);
    582                         view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
    583                     }
    584                 }
    585             }
    586         };
    587 
    588         Context context = FragmentUtil.getContext(PlaybackFragment.this);
    589         mOtherRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
    590         mOtherRowFadeInAnimator.addUpdateListener(updateListener);
    591         mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
    592 
    593         mOtherRowFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_out);
    594         mOtherRowFadeOutAnimator.addUpdateListener(updateListener);
    595         mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
    596     }
    597 
    598     /**
    599      * Fades out the playback overlay immediately.
    600      * @deprecated Call {@link #hideControlsOverlay(boolean)}
    601      */
    602     @Deprecated
    603     public void fadeOut() {
    604         showControlsOverlay(false, false);
    605     }
    606 
    607     /**
    608      * Show controls overlay.
    609      *
    610      * @param runAnimation True to run animation, false otherwise.
    611      */
    612     public void showControlsOverlay(boolean runAnimation) {
    613         showControlsOverlay(true, runAnimation);
    614     }
    615 
    616     /**
    617      * Returns true if controls overlay is visible, false otherwise.
    618      *
    619      * @return True if controls overlay is visible, false otherwise.
    620      * @see #showControlsOverlay(boolean)
    621      * @see #hideControlsOverlay(boolean)
    622      */
    623     public boolean isControlsOverlayVisible() {
    624         return mControlVisible;
    625     }
    626 
    627     /**
    628      * Hide controls overlay.
    629      *
    630      * @param runAnimation True to run animation, false otherwise.
    631      */
    632     public void hideControlsOverlay(boolean runAnimation) {
    633         showControlsOverlay(false, runAnimation);
    634     }
    635 
    636     /**
    637      * if first animator is still running, reverse it; otherwise start second animator.
    638      */
    639     static void reverseFirstOrStartSecond(ValueAnimator first, ValueAnimator second,
    640             boolean runAnimation) {
    641         if (first.isStarted()) {
    642             first.reverse();
    643             if (!runAnimation) {
    644                 first.end();
    645             }
    646         } else {
    647             second.start();
    648             if (!runAnimation) {
    649                 second.end();
    650             }
    651         }
    652     }
    653 
    654     /**
    655      * End first or second animator if they are still running.
    656      */
    657     static void endAll(ValueAnimator first, ValueAnimator second) {
    658         if (first.isStarted()) {
    659             first.end();
    660         } else if (second.isStarted()) {
    661             second.end();
    662         }
    663     }
    664 
    665     /**
    666      * Fade in or fade out rows and background.
    667      *
    668      * @param show True to fade in, false to fade out.
    669      * @param animation True to run animation.
    670      */
    671     void showControlsOverlay(boolean show, boolean animation) {
    672         if (DEBUG) Log.v(TAG, "showControlsOverlay " + show);
    673         if (getView() == null) {
    674             mControlVisibleBeforeOnCreateView = show;
    675             return;
    676         }
    677         // force no animation when fragment is not resumed
    678         if (!isResumed()) {
    679             animation = false;
    680         }
    681         if (show == mControlVisible) {
    682             if (!animation) {
    683                 // End animation if needed
    684                 endAll(mBgFadeInAnimator, mBgFadeOutAnimator);
    685                 endAll(mControlRowFadeInAnimator, mControlRowFadeOutAnimator);
    686                 endAll(mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator);
    687             }
    688             return;
    689         }
    690         // StateGraph: 7<->5 4<->3 2->3
    691         mControlVisible = show;
    692         if (!mControlVisible) {
    693             // StateGraph 2->3
    694             stopFadeTimer();
    695         }
    696 
    697         mAnimationTranslateY = (getVerticalGridView() == null
    698                 || getVerticalGridView().getSelectedPosition() == 0)
    699                 ? mMajorFadeTranslateY : mMinorFadeTranslateY;
    700 
    701         if (show) {
    702             reverseFirstOrStartSecond(mBgFadeOutAnimator, mBgFadeInAnimator, animation);
    703             reverseFirstOrStartSecond(mControlRowFadeOutAnimator, mControlRowFadeInAnimator,
    704                     animation);
    705             reverseFirstOrStartSecond(mOtherRowFadeOutAnimator, mOtherRowFadeInAnimator, animation);
    706         } else {
    707             reverseFirstOrStartSecond(mBgFadeInAnimator, mBgFadeOutAnimator, animation);
    708             reverseFirstOrStartSecond(mControlRowFadeInAnimator, mControlRowFadeOutAnimator,
    709                     animation);
    710             reverseFirstOrStartSecond(mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator, animation);
    711         }
    712         if (animation) {
    713             getView().announceForAccessibility(getString(show
    714                     ? R.string.lb_playback_controls_shown
    715                     : R.string.lb_playback_controls_hidden));
    716         }
    717     }
    718 
    719     /**
    720      * Sets the selected row position with smooth animation.
    721      */
    722     public void setSelectedPosition(int position) {
    723         setSelectedPosition(position, true);
    724     }
    725 
    726     /**
    727      * Sets the selected row position.
    728      */
    729     public void setSelectedPosition(int position, boolean smooth) {
    730         mSetSelectionRunnable.mPosition = position;
    731         mSetSelectionRunnable.mSmooth = smooth;
    732         if (getView() != null && getView().getHandler() != null) {
    733             getView().getHandler().post(mSetSelectionRunnable);
    734         }
    735     }
    736 
    737     private void setupChildFragmentLayout() {
    738         setVerticalGridViewLayout(mRowsFragment.getVerticalGridView());
    739     }
    740 
    741     void setVerticalGridViewLayout(VerticalGridView listview) {
    742         if (listview == null) {
    743             return;
    744         }
    745 
    746         // we set the base line of alignment to -paddingBottom
    747         listview.setWindowAlignmentOffset(-mPaddingBottom);
    748         listview.setWindowAlignmentOffsetPercent(
    749                 VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
    750 
    751         // align other rows that arent the last to center of screen, since our baseline is
    752         // -mPaddingBottom, we need subtract that from mOtherRowsCenterToBottom.
    753         listview.setItemAlignmentOffset(mOtherRowsCenterToBottom - mPaddingBottom);
    754         listview.setItemAlignmentOffsetPercent(50);
    755 
    756         // Push last row to the bottom padding
    757         // Padding affects alignment when last row is focused
    758         listview.setPadding(listview.getPaddingLeft(), listview.getPaddingTop(),
    759                 listview.getPaddingRight(), mPaddingBottom);
    760         listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
    761     }
    762 
    763     @Override
    764     public void onCreate(Bundle savedInstanceState) {
    765         super.onCreate(savedInstanceState);
    766 
    767         mOtherRowsCenterToBottom = getResources()
    768                 .getDimensionPixelSize(R.dimen.lb_playback_other_rows_center_to_bottom);
    769         mPaddingBottom =
    770                 getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
    771         mBgDarkColor =
    772                 getResources().getColor(R.color.lb_playback_controls_background_dark);
    773         mBgLightColor =
    774                 getResources().getColor(R.color.lb_playback_controls_background_light);
    775         mShowTimeMs =
    776                 getResources().getInteger(R.integer.lb_playback_controls_show_time_ms);
    777         mMajorFadeTranslateY =
    778                 getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
    779         mMinorFadeTranslateY =
    780                 getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
    781 
    782         loadBgAnimator();
    783         loadControlRowAnimator();
    784         loadOtherRowAnimator();
    785     }
    786 
    787     /**
    788      * Sets the background type.
    789      *
    790      * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
    791      */
    792     public void setBackgroundType(int type) {
    793         switch (type) {
    794             case BG_LIGHT:
    795             case BG_DARK:
    796             case BG_NONE:
    797                 if (type != mBackgroundType) {
    798                     mBackgroundType = type;
    799                     updateBackground();
    800                 }
    801                 break;
    802             default:
    803                 throw new IllegalArgumentException("Invalid background type");
    804         }
    805     }
    806 
    807     /**
    808      * Returns the background type.
    809      */
    810     public int getBackgroundType() {
    811         return mBackgroundType;
    812     }
    813 
    814     private void updateBackground() {
    815         if (mBackgroundView != null) {
    816             int color = mBgDarkColor;
    817             switch (mBackgroundType) {
    818                 case BG_DARK:
    819                     break;
    820                 case BG_LIGHT:
    821                     color = mBgLightColor;
    822                     break;
    823                 case BG_NONE:
    824                     color = Color.TRANSPARENT;
    825                     break;
    826             }
    827             mBackgroundView.setBackground(new ColorDrawable(color));
    828             setBgAlpha(mBgAlpha);
    829         }
    830     }
    831 
    832     private final ItemBridgeAdapter.AdapterListener mAdapterListener =
    833             new ItemBridgeAdapter.AdapterListener() {
    834                 @Override
    835                 public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
    836                     if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
    837                     if (!mControlVisible) {
    838                         if (DEBUG) Log.v(TAG, "setting alpha to 0");
    839                         vh.getViewHolder().view.setAlpha(0);
    840                     }
    841                 }
    842 
    843                 @Override
    844                 public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
    845                     Presenter.ViewHolder viewHolder = vh.getViewHolder();
    846                     if (viewHolder instanceof PlaybackSeekUi) {
    847                         ((PlaybackSeekUi) viewHolder).setPlaybackSeekUiClient(mChainedClient);
    848                     }
    849                 }
    850 
    851                 @Override
    852                 public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
    853                     if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
    854                     // Reset animation state
    855                     vh.getViewHolder().view.setAlpha(1f);
    856                     vh.getViewHolder().view.setTranslationY(0);
    857                     vh.getViewHolder().view.setAlpha(1f);
    858                 }
    859 
    860                 @Override
    861                 public void onBind(ItemBridgeAdapter.ViewHolder vh) {
    862                 }
    863             };
    864 
    865     @Override
    866     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    867                              Bundle savedInstanceState) {
    868         mRootView = inflater.inflate(R.layout.lb_playback_fragment, container, false);
    869         mBackgroundView = mRootView.findViewById(R.id.playback_fragment_background);
    870         mRowsFragment = (RowsFragment) getChildFragmentManager().findFragmentById(
    871                 R.id.playback_controls_dock);
    872         if (mRowsFragment == null) {
    873             mRowsFragment = new RowsFragment();
    874             getChildFragmentManager().beginTransaction()
    875                     .replace(R.id.playback_controls_dock, mRowsFragment)
    876                     .commit();
    877         }
    878         if (mAdapter == null) {
    879             setAdapter(new ArrayObjectAdapter(new ClassPresenterSelector()));
    880         } else {
    881             mRowsFragment.setAdapter(mAdapter);
    882         }
    883         mRowsFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
    884         mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
    885 
    886         mBgAlpha = 255;
    887         updateBackground();
    888         mRowsFragment.setExternalAdapterListener(mAdapterListener);
    889         ProgressBarManager progressBarManager = getProgressBarManager();
    890         if (progressBarManager != null) {
    891             progressBarManager.setRootView((ViewGroup) mRootView);
    892         }
    893         return mRootView;
    894     }
    895 
    896     /**
    897      * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
    898      * take appropriate actions to take action when the hosting fragment starts/stops processing.
    899      */
    900     public void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
    901         this.mHostCallback = hostCallback;
    902     }
    903 
    904     @Override
    905     public void onStart() {
    906         super.onStart();
    907         setupChildFragmentLayout();
    908         mRowsFragment.setAdapter(mAdapter);
    909         if (mHostCallback != null) {
    910             mHostCallback.onHostStart();
    911         }
    912     }
    913 
    914     @Override
    915     public void onStop() {
    916         if (mHostCallback != null) {
    917             mHostCallback.onHostStop();
    918         }
    919         super.onStop();
    920     }
    921 
    922     @Override
    923     public void onPause() {
    924         if (mHostCallback != null) {
    925             mHostCallback.onHostPause();
    926         }
    927         if (mHandler.hasMessages(START_FADE_OUT)) {
    928             // StateGraph: 2->1
    929             mHandler.removeMessages(START_FADE_OUT);
    930         } else {
    931             // StateGraph: 5->6, 7->6, 4->1, 3->1
    932         }
    933         super.onPause();
    934     }
    935 
    936     /**
    937      * This listener is called every time there is a selection in {@link RowsFragment}. This can
    938      * be used by users to take additional actions such as animations.
    939      */
    940     public void setOnItemViewSelectedListener(final BaseOnItemViewSelectedListener listener) {
    941         mExternalItemSelectedListener = listener;
    942     }
    943 
    944     /**
    945      * This listener is called every time there is a click in {@link RowsFragment}. This can
    946      * be used by users to take additional actions such as animations.
    947      */
    948     public void setOnItemViewClickedListener(final BaseOnItemViewClickedListener listener) {
    949         mExternalItemClickedListener = listener;
    950     }
    951 
    952     /**
    953      * Sets the {@link BaseOnItemViewClickedListener} that would be invoked for clicks
    954      * only on {@link androidx.leanback.widget.PlaybackRowPresenter.ViewHolder}.
    955      */
    956     public void setOnPlaybackItemViewClickedListener(final BaseOnItemViewClickedListener listener) {
    957         mPlaybackItemClickedListener = listener;
    958     }
    959 
    960     @Override
    961     public void onDestroyView() {
    962         mRootView = null;
    963         mBackgroundView = null;
    964         super.onDestroyView();
    965     }
    966 
    967     @Override
    968     public void onDestroy() {
    969         if (mHostCallback != null) {
    970             mHostCallback.onHostDestroy();
    971         }
    972         super.onDestroy();
    973     }
    974 
    975     /**
    976      * Sets the playback row for the playback controls. The row will be set as first element
    977      * of adapter if the adapter is {@link ArrayObjectAdapter} or {@link SparseArrayObjectAdapter}.
    978      * @param row The row that represents the playback.
    979      */
    980     public void setPlaybackRow(Row row) {
    981         this.mRow = row;
    982         setupRow();
    983         setupPresenter();
    984     }
    985 
    986     /**
    987      * Sets the presenter for rendering the playback row set by {@link #setPlaybackRow(Row)}. If
    988      * adapter does not set a {@link PresenterSelector}, {@link #setAdapter(ObjectAdapter)} will
    989      * create a {@link ClassPresenterSelector} by default and map from the row object class to this
    990      * {@link PlaybackRowPresenter}.
    991      *
    992      * @param  presenter Presenter used to render {@link #setPlaybackRow(Row)}.
    993      */
    994     public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {
    995         this.mPresenter = presenter;
    996         setupPresenter();
    997         setPlaybackRowPresenterAlignment();
    998     }
    999 
   1000     void setPlaybackRowPresenterAlignment() {
   1001         if (mAdapter != null && mAdapter.getPresenterSelector() != null) {
   1002             Presenter[] presenters = mAdapter.getPresenterSelector().getPresenters();
   1003             if (presenters != null) {
   1004                 for (int i = 0; i < presenters.length; i++) {
   1005                     if (presenters[i] instanceof PlaybackRowPresenter
   1006                             && presenters[i].getFacet(ItemAlignmentFacet.class) == null) {
   1007                         ItemAlignmentFacet itemAlignment = new ItemAlignmentFacet();
   1008                         ItemAlignmentFacet.ItemAlignmentDef def =
   1009                                 new ItemAlignmentFacet.ItemAlignmentDef();
   1010                         def.setItemAlignmentOffset(0);
   1011                         def.setItemAlignmentOffsetPercent(100);
   1012                         itemAlignment.setAlignmentDefs(new ItemAlignmentFacet.ItemAlignmentDef[]
   1013                                 {def});
   1014                         presenters[i].setFacet(ItemAlignmentFacet.class, itemAlignment);
   1015                     }
   1016                 }
   1017             }
   1018         }
   1019     }
   1020 
   1021     /**
   1022      * Updates the ui when the row data changes.
   1023      */
   1024     public void notifyPlaybackRowChanged() {
   1025         if (mAdapter == null) {
   1026             return;
   1027         }
   1028         mAdapter.notifyItemRangeChanged(0, 1);
   1029     }
   1030 
   1031     /**
   1032      * Sets the list of rows for the fragment. A default {@link ClassPresenterSelector} will be
   1033      * created if {@link ObjectAdapter#getPresenterSelector()} is null. if user provides
   1034      * {@link #setPlaybackRow(Row)} and {@link #setPlaybackRowPresenter(PlaybackRowPresenter)},
   1035      * the row and presenter will be set onto the adapter.
   1036      *
   1037      * @param adapter The adapter that contains related rows and optional playback row.
   1038      */
   1039     public void setAdapter(ObjectAdapter adapter) {
   1040         mAdapter = adapter;
   1041         setupRow();
   1042         setupPresenter();
   1043         setPlaybackRowPresenterAlignment();
   1044 
   1045         if (mRowsFragment != null) {
   1046             mRowsFragment.setAdapter(adapter);
   1047         }
   1048     }
   1049 
   1050     private void setupRow() {
   1051         if (mAdapter instanceof ArrayObjectAdapter && mRow != null) {
   1052             ArrayObjectAdapter adapter = ((ArrayObjectAdapter) mAdapter);
   1053             if (adapter.size() == 0) {
   1054                 adapter.add(mRow);
   1055             } else {
   1056                 adapter.replace(0, mRow);
   1057             }
   1058         } else if (mAdapter instanceof SparseArrayObjectAdapter && mRow != null) {
   1059             SparseArrayObjectAdapter adapter = ((SparseArrayObjectAdapter) mAdapter);
   1060             adapter.set(0, mRow);
   1061         }
   1062     }
   1063 
   1064     private void setupPresenter() {
   1065         if (mAdapter != null && mRow != null && mPresenter != null) {
   1066             PresenterSelector selector = mAdapter.getPresenterSelector();
   1067             if (selector == null) {
   1068                 selector = new ClassPresenterSelector();
   1069                 ((ClassPresenterSelector) selector).addClassPresenter(mRow.getClass(), mPresenter);
   1070                 mAdapter.setPresenterSelector(selector);
   1071             } else if (selector instanceof ClassPresenterSelector) {
   1072                 ((ClassPresenterSelector) selector).addClassPresenter(mRow.getClass(), mPresenter);
   1073             }
   1074         }
   1075     }
   1076 
   1077     final PlaybackSeekUi.Client mChainedClient = new PlaybackSeekUi.Client() {
   1078         @Override
   1079         public boolean isSeekEnabled() {
   1080             return mSeekUiClient == null ? false : mSeekUiClient.isSeekEnabled();
   1081         }
   1082 
   1083         @Override
   1084         public void onSeekStarted() {
   1085             if (mSeekUiClient != null) {
   1086                 mSeekUiClient.onSeekStarted();
   1087             }
   1088             setSeekMode(true);
   1089         }
   1090 
   1091         @Override
   1092         public PlaybackSeekDataProvider getPlaybackSeekDataProvider() {
   1093             return mSeekUiClient == null ? null : mSeekUiClient.getPlaybackSeekDataProvider();
   1094         }
   1095 
   1096         @Override
   1097         public void onSeekPositionChanged(long pos) {
   1098             if (mSeekUiClient != null) {
   1099                 mSeekUiClient.onSeekPositionChanged(pos);
   1100             }
   1101         }
   1102 
   1103         @Override
   1104         public void onSeekFinished(boolean cancelled) {
   1105             if (mSeekUiClient != null) {
   1106                 mSeekUiClient.onSeekFinished(cancelled);
   1107             }
   1108             setSeekMode(false);
   1109         }
   1110     };
   1111 
   1112     /**
   1113      * Interface to be implemented by UI widget to support PlaybackSeekUi.
   1114      */
   1115     public void setPlaybackSeekUiClient(PlaybackSeekUi.Client client) {
   1116         mSeekUiClient = client;
   1117     }
   1118 
   1119     /**
   1120      * Show or hide other rows other than PlaybackRow.
   1121      * @param inSeek True to make other rows visible, false to make other rows invisible.
   1122      */
   1123     void setSeekMode(boolean inSeek) {
   1124         if (mInSeek == inSeek) {
   1125             return;
   1126         }
   1127         mInSeek = inSeek;
   1128         getVerticalGridView().setSelectedPosition(0);
   1129         if (mInSeek) {
   1130             stopFadeTimer();
   1131         }
   1132         // immediately fade in control row.
   1133         showControlsOverlay(true);
   1134         final int count = getVerticalGridView().getChildCount();
   1135         for (int i = 0; i < count; i++) {
   1136             View view = getVerticalGridView().getChildAt(i);
   1137             if (getVerticalGridView().getChildAdapterPosition(view) > 0) {
   1138                 view.setVisibility(mInSeek ? View.INVISIBLE : View.VISIBLE);
   1139             }
   1140         }
   1141     }
   1142 
   1143     /**
   1144      * Called when size of the video changes. App may override.
   1145      * @param videoWidth Intrinsic width of video
   1146      * @param videoHeight Intrinsic height of video
   1147      */
   1148     protected void onVideoSizeChanged(int videoWidth, int videoHeight) {
   1149     }
   1150 
   1151     /**
   1152      * Called when media has start or stop buffering. App may override. The default initial state
   1153      * is not buffering.
   1154      * @param start True for buffering start, false otherwise.
   1155      */
   1156     protected void onBufferingStateChanged(boolean start) {
   1157         ProgressBarManager progressBarManager = getProgressBarManager();
   1158         if (progressBarManager != null) {
   1159             if (start) {
   1160                 progressBarManager.show();
   1161             } else {
   1162                 progressBarManager.hide();
   1163             }
   1164         }
   1165     }
   1166 
   1167     /**
   1168      * Called when media has error. App may override.
   1169      * @param errorCode Optional error code for specific implementation.
   1170      * @param errorMessage Optional error message for specific implementation.
   1171      */
   1172     protected void onError(int errorCode, CharSequence errorMessage) {
   1173     }
   1174 
   1175     /**
   1176      * Returns the ProgressBarManager that will show or hide progress bar in
   1177      * {@link #onBufferingStateChanged(boolean)}.
   1178      * @return The ProgressBarManager that will show or hide progress bar in
   1179      * {@link #onBufferingStateChanged(boolean)}.
   1180      */
   1181     public ProgressBarManager getProgressBarManager() {
   1182         return mProgressBarManager;
   1183     }
   1184 }
   1185