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