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