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