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