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