• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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)
resetFocus()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
run()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 
getAdapter()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 {
onFadeInComplete()194         public void onFadeInComplete() {
195         }
196 
onFadeOutComplete()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 
PlaybackFragment()272     public PlaybackFragment() {
273         mProgressBarManager.setInitialDelay(500);
274     }
275 
getVerticalGridView()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 
setBgAlpha(int alpha)308     private void setBgAlpha(int alpha) {
309         mBgAlpha = alpha;
310         if (mBackgroundView != null) {
311             mBackgroundView.getBackground().setAlpha(alpha);
312         }
313     }
314 
enableVerticalGridAnimations(boolean enable)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      */
setControlsOverlayAutoHideEnabled(boolean enabled)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      */
isControlsOverlayAutoHideEnabled()349     public boolean isControlsOverlayAutoHideEnabled() {
350         return mFadingEnabled;
351     }
352 
353     /**
354      * @deprecated Uses {@link #setControlsOverlayAutoHideEnabled(boolean)}
355      */
356     @Deprecated
setFadingEnabled(boolean enabled)357     public void setFadingEnabled(boolean enabled) {
358         setControlsOverlayAutoHideEnabled(enabled);
359     }
360 
361     /**
362      * @deprecated Uses {@link #isControlsOverlayAutoHideEnabled()}
363      */
364     @Deprecated
isFadingEnabled()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)
setFadeCompleteListener(OnFadeCompleteListener listener)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)
getFadeCompleteListener()383     public OnFadeCompleteListener getFadeCompleteListener() {
384         return mFadeCompleteListener;
385     }
386 
387     /**
388      * Sets the input event handler.
389      */
setOnKeyInterceptListener(View.OnKeyListener handler)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      */
tickle()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 
onInterceptInputEvent(InputEvent event)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
onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)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
onResume()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 
stopFadeTimer()496     private void stopFadeTimer() {
497         if (mHandler != null) {
498             mHandler.removeMessages(START_FADE_OUT);
499         }
500     }
501 
startFadeTimer()502     private void startFadeTimer() {
503         if (mHandler != null) {
504             mHandler.removeMessages(START_FADE_OUT);
505             mHandler.sendEmptyMessageDelayed(START_FADE_OUT, mShowTimeMs);
506         }
507     }
508 
loadAnimator(Context context, int resId)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 
loadBgAnimator()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 
loadControlRowAnimator()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 
loadOtherRowAnimator()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
fadeOut()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      */
showControlsOverlay(boolean runAnimation)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      */
isControlsOverlayVisible()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      */
hideControlsOverlay(boolean runAnimation)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      */
reverseFirstOrStartSecond(ValueAnimator first, ValueAnimator second, boolean runAnimation)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      */
endAll(ValueAnimator first, ValueAnimator second)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      */
showControlsOverlay(boolean show, boolean animation)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      */
setSelectedPosition(int position)722     public void setSelectedPosition(int position) {
723         setSelectedPosition(position, true);
724     }
725 
726     /**
727      * Sets the selected row position.
728      */
setSelectedPosition(int position, boolean smooth)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 
setupChildFragmentLayout()737     private void setupChildFragmentLayout() {
738         setVerticalGridViewLayout(mRowsFragment.getVerticalGridView());
739     }
740 
setVerticalGridViewLayout(VerticalGridView listview)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
onCreate(Bundle savedInstanceState)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      */
setBackgroundType(int type)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      */
getBackgroundType()810     public int getBackgroundType() {
811         return mBackgroundType;
812     }
813 
updateBackground()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
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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      */
setHostCallback(PlaybackGlueHost.HostCallback hostCallback)900     public void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
901         this.mHostCallback = hostCallback;
902     }
903 
904     @Override
onStart()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
onStop()915     public void onStop() {
916         if (mHostCallback != null) {
917             mHostCallback.onHostStop();
918         }
919         super.onStop();
920     }
921 
922     @Override
onPause()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      */
setOnItemViewSelectedListener(final BaseOnItemViewSelectedListener listener)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      */
setOnItemViewClickedListener(final BaseOnItemViewClickedListener listener)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      */
setOnPlaybackItemViewClickedListener(final BaseOnItemViewClickedListener listener)956     public void setOnPlaybackItemViewClickedListener(final BaseOnItemViewClickedListener listener) {
957         mPlaybackItemClickedListener = listener;
958     }
959 
960     @Override
onDestroyView()961     public void onDestroyView() {
962         mRootView = null;
963         mBackgroundView = null;
964         super.onDestroyView();
965     }
966 
967     @Override
onDestroy()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      */
setPlaybackRow(Row row)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      */
setPlaybackRowPresenter(PlaybackRowPresenter presenter)994     public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {
995         this.mPresenter = presenter;
996         setupPresenter();
997         setPlaybackRowPresenterAlignment();
998     }
999 
setPlaybackRowPresenterAlignment()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      */
notifyPlaybackRowChanged()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      */
setAdapter(ObjectAdapter adapter)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 
setupRow()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 
setupPresenter()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      */
setPlaybackSeekUiClient(PlaybackSeekUi.Client client)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      */
setSeekMode(boolean inSeek)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      */
onVideoSizeChanged(int videoWidth, int videoHeight)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      */
onBufferingStateChanged(boolean start)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      */
onError(int errorCode, CharSequence errorMessage)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      */
getProgressBarManager()1181     public ProgressBarManager getProgressBarManager() {
1182         return mProgressBarManager;
1183     }
1184 }
1185