1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14package android.support.v17.leanback.widget; 15 16import android.content.Context; 17import android.graphics.Color; 18import android.support.annotation.ColorInt; 19import android.support.v17.leanback.R; 20import android.support.v17.leanback.widget.ControlBarPresenter.OnControlClickedListener; 21import android.support.v17.leanback.widget.ControlBarPresenter.OnControlSelectedListener; 22import android.util.TypedValue; 23import android.view.KeyEvent; 24import android.view.LayoutInflater; 25import android.view.View; 26import android.view.ViewGroup; 27import android.view.ViewGroup.LayoutParams; 28import android.view.ViewGroup.MarginLayoutParams; 29import android.widget.ImageView; 30import android.widget.LinearLayout; 31 32/** 33 * A PlaybackControlsRowPresenter renders a {@link PlaybackControlsRow} to display a 34 * series of playback control buttons. Typically this row will be the first row in a fragment 35 * such as the {@link android.support.v17.leanback.app.PlaybackOverlayFragment}. 36 * 37 * <p>The detailed description is rendered using a {@link Presenter} passed in 38 * {@link #PlaybackControlsRowPresenter(Presenter)}. Typically this will be an instance of 39 * {@link AbstractDetailsDescriptionPresenter}. The application can access the 40 * detailed description ViewHolder from {@link ViewHolder#mDescriptionViewHolder}. 41 * </p> 42 */ 43public class PlaybackControlsRowPresenter extends PlaybackRowPresenter { 44 45 static class BoundData extends PlaybackControlsPresenter.BoundData { 46 ViewHolder mRowViewHolder; 47 } 48 49 /** 50 * A ViewHolder for the PlaybackControlsRow. 51 */ 52 public class ViewHolder extends PlaybackRowPresenter.ViewHolder { 53 public final Presenter.ViewHolder mDescriptionViewHolder; 54 final ViewGroup mCard; 55 final ViewGroup mCardRightPanel; 56 final ImageView mImageView; 57 final ViewGroup mDescriptionDock; 58 final ViewGroup mControlsDock; 59 final ViewGroup mSecondaryControlsDock; 60 final View mSpacer; 61 final View mBottomSpacer; 62 View mBgView; 63 int mControlsDockMarginStart; 64 int mControlsDockMarginEnd; 65 PlaybackControlsPresenter.ViewHolder mControlsVh; 66 Presenter.ViewHolder mSecondaryControlsVh; 67 BoundData mControlsBoundData = new BoundData(); 68 BoundData mSecondaryBoundData = new BoundData(); 69 Presenter.ViewHolder mSelectedViewHolder; 70 Object mSelectedItem; 71 final PlaybackControlsRow.OnPlaybackProgressCallback mListener = 72 new PlaybackControlsRow.OnPlaybackProgressCallback() { 73 @Override 74 public void onCurrentPositionChanged(PlaybackControlsRow row, long ms) { 75 mPlaybackControlsPresenter.setCurrentTimeLong(mControlsVh, ms); 76 } 77 78 @Override 79 public void onDurationChanged(PlaybackControlsRow row, long ms) { 80 mPlaybackControlsPresenter.setTotalTimeLong(mControlsVh, ms); 81 } 82 83 @Override 84 public void onBufferedPositionChanged(PlaybackControlsRow row, long ms) { 85 mPlaybackControlsPresenter.setSecondaryProgressLong(mControlsVh, ms); 86 } 87 }; 88 89 ViewHolder(View rootView, Presenter descriptionPresenter) { 90 super(rootView); 91 mCard = (ViewGroup) rootView.findViewById(R.id.controls_card); 92 mCardRightPanel = (ViewGroup) rootView.findViewById(R.id.controls_card_right_panel); 93 mImageView = (ImageView) rootView.findViewById(R.id.image); 94 mDescriptionDock = (ViewGroup) rootView.findViewById(R.id.description_dock); 95 mControlsDock = (ViewGroup) rootView.findViewById(R.id.controls_dock); 96 mSecondaryControlsDock = 97 (ViewGroup) rootView.findViewById(R.id.secondary_controls_dock); 98 mSpacer = rootView.findViewById(R.id.spacer); 99 mBottomSpacer = rootView.findViewById(R.id.bottom_spacer); 100 mDescriptionViewHolder = descriptionPresenter == null ? null : 101 descriptionPresenter.onCreateViewHolder(mDescriptionDock); 102 if (mDescriptionViewHolder != null) { 103 mDescriptionDock.addView(mDescriptionViewHolder.view); 104 } 105 } 106 107 void dispatchItemSelection() { 108 if (!isSelected()) { 109 return; 110 } 111 if (mSelectedViewHolder == null) { 112 if (getOnItemViewSelectedListener() != null) { 113 getOnItemViewSelectedListener().onItemSelected(null, null, 114 ViewHolder.this, getRow()); 115 } 116 } else { 117 if (getOnItemViewSelectedListener() != null) { 118 getOnItemViewSelectedListener().onItemSelected(mSelectedViewHolder, mSelectedItem, 119 ViewHolder.this, getRow()); 120 } 121 } 122 }; 123 124 Presenter getPresenter(boolean primary) { 125 ObjectAdapter adapter = primary 126 ? ((PlaybackControlsRow) getRow()).getPrimaryActionsAdapter() 127 : ((PlaybackControlsRow) getRow()).getSecondaryActionsAdapter(); 128 if (adapter == null) { 129 return null; 130 } 131 if (adapter.getPresenterSelector() instanceof ControlButtonPresenterSelector) { 132 ControlButtonPresenterSelector selector = 133 (ControlButtonPresenterSelector) adapter.getPresenterSelector(); 134 return primary ? selector.getPrimaryPresenter() 135 : selector.getSecondaryPresenter(); 136 } 137 return adapter.getPresenter(adapter.size() > 0 ? adapter.get(0) : null); 138 } 139 140 void setOutline(View view) { 141 if (mBgView != null) { 142 RoundedRectHelper.getInstance().setClipToRoundedOutline(mBgView, false); 143 ShadowHelper.getInstance().setZ(mBgView, 0); 144 } 145 mBgView = view; 146 RoundedRectHelper.getInstance().setClipToRoundedOutline(view, true); 147 148 if (sShadowZ == 0) { 149 sShadowZ = view.getResources().getDimensionPixelSize( 150 R.dimen.lb_playback_controls_z); 151 } 152 ShadowHelper.getInstance().setZ(view, sShadowZ); 153 } 154 } 155 156 private int mBackgroundColor = Color.TRANSPARENT; 157 private boolean mBackgroundColorSet; 158 private int mProgressColor = Color.TRANSPARENT; 159 private boolean mProgressColorSet; 160 private boolean mSecondaryActionsHidden; 161 private Presenter mDescriptionPresenter; 162 PlaybackControlsPresenter mPlaybackControlsPresenter; 163 private ControlBarPresenter mSecondaryControlsPresenter; 164 OnActionClickedListener mOnActionClickedListener; 165 static float sShadowZ; 166 167 private final OnControlSelectedListener mOnControlSelectedListener = 168 new OnControlSelectedListener() { 169 @Override 170 public void onControlSelected(Presenter.ViewHolder itemViewHolder, Object item, 171 ControlBarPresenter.BoundData data) { 172 ViewHolder vh = ((BoundData) data).mRowViewHolder; 173 if (vh.mSelectedViewHolder != itemViewHolder || vh.mSelectedItem != item) { 174 vh.mSelectedViewHolder = itemViewHolder; 175 vh.mSelectedItem = item; 176 vh.dispatchItemSelection(); 177 } 178 } 179 }; 180 181 private final OnControlClickedListener mOnControlClickedListener = 182 new OnControlClickedListener() { 183 @Override 184 public void onControlClicked(Presenter.ViewHolder itemViewHolder, Object item, 185 ControlBarPresenter.BoundData data) { 186 ViewHolder vh = ((BoundData) data).mRowViewHolder; 187 if (vh.getOnItemViewClickedListener() != null) { 188 vh.getOnItemViewClickedListener().onItemClicked(itemViewHolder, item, 189 vh, vh.getRow()); 190 } 191 if (mOnActionClickedListener != null && item instanceof Action) { 192 mOnActionClickedListener.onActionClicked((Action) item); 193 } 194 } 195 }; 196 197 /** 198 * Constructor for a PlaybackControlsRowPresenter. 199 * 200 * @param descriptionPresenter Presenter for displaying item details. 201 */ 202 public PlaybackControlsRowPresenter(Presenter descriptionPresenter) { 203 setHeaderPresenter(null); 204 setSelectEffectEnabled(false); 205 206 mDescriptionPresenter = descriptionPresenter; 207 mPlaybackControlsPresenter = new PlaybackControlsPresenter(R.layout.lb_playback_controls); 208 mSecondaryControlsPresenter = new ControlBarPresenter(R.layout.lb_control_bar); 209 210 mPlaybackControlsPresenter.setOnControlSelectedListener(mOnControlSelectedListener); 211 mSecondaryControlsPresenter.setOnControlSelectedListener(mOnControlSelectedListener); 212 mPlaybackControlsPresenter.setOnControlClickedListener(mOnControlClickedListener); 213 mSecondaryControlsPresenter.setOnControlClickedListener(mOnControlClickedListener); 214 } 215 216 /** 217 * Constructor for a PlaybackControlsRowPresenter. 218 */ 219 public PlaybackControlsRowPresenter() { 220 this(null); 221 } 222 223 /** 224 * Sets the listener for {@link Action} click events. 225 */ 226 public void setOnActionClickedListener(OnActionClickedListener listener) { 227 mOnActionClickedListener = listener; 228 } 229 230 /** 231 * Returns the listener for {@link Action} click events. 232 */ 233 public OnActionClickedListener getOnActionClickedListener() { 234 return mOnActionClickedListener; 235 } 236 237 /** 238 * Sets the background color. If not set, a default from the theme will be used. 239 */ 240 public void setBackgroundColor(@ColorInt int color) { 241 mBackgroundColor = color; 242 mBackgroundColorSet = true; 243 } 244 245 /** 246 * Returns the background color. If no background color was set, transparent 247 * is returned. 248 */ 249 @ColorInt 250 public int getBackgroundColor() { 251 return mBackgroundColor; 252 } 253 254 /** 255 * Sets the primary color for the progress bar. If not set, a default from 256 * the theme will be used. 257 */ 258 public void setProgressColor(@ColorInt int color) { 259 mProgressColor = color; 260 mProgressColorSet = true; 261 } 262 263 /** 264 * Returns the primary color for the progress bar. If no color was set, transparent 265 * is returned. 266 */ 267 @ColorInt 268 public int getProgressColor() { 269 return mProgressColor; 270 } 271 272 /** 273 * Sets the secondary actions to be hidden behind a "more actions" button. 274 * When "more actions" is selected, the primary actions are replaced with 275 * the secondary actions. 276 */ 277 public void setSecondaryActionsHidden(boolean hidden) { 278 mSecondaryActionsHidden = hidden; 279 } 280 281 /** 282 * Returns true if secondary actions are hidden. 283 */ 284 public boolean areSecondaryActionsHidden() { 285 return mSecondaryActionsHidden; 286 } 287 288 /** 289 * Shows or hides space at the bottom of the playback controls row. 290 * This allows the row to hug the bottom of the display when no 291 * other rows are present. 292 */ 293 public void showBottomSpace(ViewHolder vh, boolean show) { 294 vh.mBottomSpacer.setVisibility(show ? View.VISIBLE : View.GONE); 295 } 296 297 /** 298 * Displays the primary actions. This will override the user having selected "more actions" 299 * to display the secondary actions; see {@link #setSecondaryActionsHidden(boolean)}. 300 */ 301 public void showPrimaryActions(ViewHolder vh) { 302 mPlaybackControlsPresenter.showPrimaryActions(vh.mControlsVh); 303 if (vh.view.hasFocus()) { 304 mPlaybackControlsPresenter.resetFocus(vh.mControlsVh); 305 } 306 } 307 308 @Override 309 public void onReappear(RowPresenter.ViewHolder rowViewHolder) { 310 showPrimaryActions((ViewHolder) rowViewHolder); 311 } 312 313 private int getDefaultBackgroundColor(Context context) { 314 TypedValue outValue = new TypedValue(); 315 if (context.getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true)) { 316 return context.getResources().getColor(outValue.resourceId); 317 } 318 return context.getResources().getColor(R.color.lb_default_brand_color); 319 } 320 321 private int getDefaultProgressColor(Context context) { 322 TypedValue outValue = new TypedValue(); 323 if (context.getTheme() 324 .resolveAttribute(R.attr.playbackProgressPrimaryColor, outValue, true)) { 325 return context.getResources().getColor(outValue.resourceId); 326 } 327 return context.getResources().getColor(R.color.lb_playback_progress_color_no_theme); 328 } 329 330 @Override 331 protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) { 332 View v = LayoutInflater.from(parent.getContext()) 333 .inflate(R.layout.lb_playback_controls_row, parent, false); 334 ViewHolder vh = new ViewHolder(v, mDescriptionPresenter); 335 initRow(vh); 336 return vh; 337 } 338 339 private void initRow(final ViewHolder vh) { 340 MarginLayoutParams lp = (MarginLayoutParams) vh.mControlsDock.getLayoutParams(); 341 vh.mControlsDockMarginStart = lp.getMarginStart(); 342 vh.mControlsDockMarginEnd = lp.getMarginEnd(); 343 344 vh.mControlsVh = (PlaybackControlsPresenter.ViewHolder) 345 mPlaybackControlsPresenter.onCreateViewHolder(vh.mControlsDock); 346 mPlaybackControlsPresenter.setProgressColor(vh.mControlsVh, mProgressColorSet 347 ? mProgressColor : getDefaultProgressColor(vh.mControlsDock.getContext())); 348 mPlaybackControlsPresenter.setBackgroundColor(vh.mControlsVh, mBackgroundColorSet 349 ? mBackgroundColor : getDefaultBackgroundColor(vh.view.getContext())); 350 vh.mControlsDock.addView(vh.mControlsVh.view); 351 352 vh.mSecondaryControlsVh = 353 mSecondaryControlsPresenter.onCreateViewHolder(vh.mSecondaryControlsDock); 354 if (!mSecondaryActionsHidden) { 355 vh.mSecondaryControlsDock.addView(vh.mSecondaryControlsVh.view); 356 } 357 ((PlaybackControlsRowView) vh.view).setOnUnhandledKeyListener( 358 new PlaybackControlsRowView.OnUnhandledKeyListener() { 359 @Override 360 public boolean onUnhandledKey(KeyEvent event) { 361 if (vh.getOnKeyListener() != null) { 362 if (vh.getOnKeyListener().onKey(vh.view, event.getKeyCode(), event)) { 363 return true; 364 } 365 } 366 return false; 367 } 368 }); 369 } 370 371 @Override 372 protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) { 373 super.onBindRowViewHolder(holder, item); 374 375 ViewHolder vh = (ViewHolder) holder; 376 PlaybackControlsRow row = (PlaybackControlsRow) vh.getRow(); 377 378 mPlaybackControlsPresenter.enableSecondaryActions(mSecondaryActionsHidden); 379 380 if (row.getItem() == null) { 381 vh.mDescriptionDock.setVisibility(View.GONE); 382 vh.mSpacer.setVisibility(View.GONE); 383 } else { 384 vh.mDescriptionDock.setVisibility(View.VISIBLE); 385 if (vh.mDescriptionViewHolder != null) { 386 mDescriptionPresenter.onBindViewHolder(vh.mDescriptionViewHolder, row.getItem()); 387 } 388 vh.mSpacer.setVisibility(View.VISIBLE); 389 } 390 391 if (row.getImageDrawable() == null || row.getItem() == null) { 392 vh.mImageView.setImageDrawable(null); 393 updateCardLayout(vh, LayoutParams.WRAP_CONTENT); 394 } else { 395 vh.mImageView.setImageDrawable(row.getImageDrawable()); 396 updateCardLayout(vh, vh.mImageView.getLayoutParams().height); 397 } 398 399 vh.mControlsBoundData.adapter = row.getPrimaryActionsAdapter(); 400 vh.mControlsBoundData.secondaryActionsAdapter = row.getSecondaryActionsAdapter(); 401 vh.mControlsBoundData.presenter = vh.getPresenter(true); 402 vh.mControlsBoundData.mRowViewHolder = vh; 403 mPlaybackControlsPresenter.onBindViewHolder(vh.mControlsVh, vh.mControlsBoundData); 404 405 vh.mSecondaryBoundData.adapter = row.getSecondaryActionsAdapter(); 406 vh.mSecondaryBoundData.presenter = vh.getPresenter(false); 407 vh.mSecondaryBoundData.mRowViewHolder = vh; 408 mSecondaryControlsPresenter.onBindViewHolder(vh.mSecondaryControlsVh, 409 vh.mSecondaryBoundData); 410 411 mPlaybackControlsPresenter.setTotalTime(vh.mControlsVh, row.getTotalTime()); 412 mPlaybackControlsPresenter.setCurrentTime(vh.mControlsVh, row.getCurrentTime()); 413 mPlaybackControlsPresenter.setSecondaryProgress(vh.mControlsVh, row.getBufferedProgress()); 414 row.setOnPlaybackProgressChangedListener(vh.mListener); 415 } 416 417 private void updateCardLayout(ViewHolder vh, int height) { 418 LayoutParams lp = vh.mCardRightPanel.getLayoutParams(); 419 lp.height = height; 420 vh.mCardRightPanel.setLayoutParams(lp); 421 422 MarginLayoutParams mlp = (MarginLayoutParams) vh.mControlsDock.getLayoutParams(); 423 LinearLayout.LayoutParams llp = 424 (LinearLayout.LayoutParams) vh.mDescriptionDock.getLayoutParams(); 425 426 if (height == LayoutParams.WRAP_CONTENT) { 427 llp.height = LayoutParams.WRAP_CONTENT; 428 mlp.setMarginStart(0); 429 mlp.setMarginEnd(0); 430 vh.mCard.setBackground(null); 431 vh.setOutline(vh.mControlsDock); 432 mPlaybackControlsPresenter.enableTimeMargins(vh.mControlsVh, true); 433 } else { 434 llp.height = 0; 435 llp.weight = 1; 436 mlp.setMarginStart(vh.mControlsDockMarginStart); 437 mlp.setMarginEnd(vh.mControlsDockMarginEnd); 438 vh.mCard.setBackgroundColor(mBackgroundColorSet ? mBackgroundColor : 439 getDefaultBackgroundColor(vh.mCard.getContext())); 440 vh.setOutline(vh.mCard); 441 mPlaybackControlsPresenter.enableTimeMargins(vh.mControlsVh, false); 442 } 443 vh.mDescriptionDock.setLayoutParams(llp); 444 vh.mControlsDock.setLayoutParams(mlp); 445 } 446 447 @Override 448 protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) { 449 ViewHolder vh = (ViewHolder) holder; 450 PlaybackControlsRow row = (PlaybackControlsRow) vh.getRow(); 451 452 if (vh.mDescriptionViewHolder != null) { 453 mDescriptionPresenter.onUnbindViewHolder(vh.mDescriptionViewHolder); 454 } 455 mPlaybackControlsPresenter.onUnbindViewHolder(vh.mControlsVh); 456 mSecondaryControlsPresenter.onUnbindViewHolder(vh.mSecondaryControlsVh); 457 row.setOnPlaybackProgressChangedListener(null); 458 459 super.onUnbindRowViewHolder(holder); 460 } 461 462 @Override 463 protected void onRowViewSelected(RowPresenter.ViewHolder vh, boolean selected) { 464 super.onRowViewSelected(vh, selected); 465 if (selected) { 466 ((ViewHolder) vh).dispatchItemSelection(); 467 } 468 } 469 470 @Override 471 protected void onRowViewAttachedToWindow(RowPresenter.ViewHolder vh) { 472 super.onRowViewAttachedToWindow(vh); 473 if (mDescriptionPresenter != null) { 474 mDescriptionPresenter.onViewAttachedToWindow( 475 ((ViewHolder) vh).mDescriptionViewHolder); 476 } 477 } 478 479 @Override 480 protected void onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh) { 481 super.onRowViewDetachedFromWindow(vh); 482 if (mDescriptionPresenter != null) { 483 mDescriptionPresenter.onViewDetachedFromWindow( 484 ((ViewHolder) vh).mDescriptionViewHolder); 485 } 486 } 487} 488