1 /* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.media; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.content.res.ColorStateList; 22 import android.content.res.Resources; 23 import android.content.res.TypedArray; 24 import android.graphics.Rect; 25 import android.os.Bundle; 26 import android.util.Log; 27 import android.util.Size; 28 import android.view.LayoutInflater; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.widget.ImageView; 32 import android.widget.SeekBar; 33 import android.widget.TextView; 34 35 import androidx.annotation.NonNull; 36 import androidx.constraintlayout.widget.ConstraintLayout; 37 import androidx.fragment.app.Fragment; 38 import androidx.lifecycle.ViewModelProviders; 39 import androidx.recyclerview.widget.DefaultItemAnimator; 40 import androidx.recyclerview.widget.LinearLayoutManager; 41 import androidx.recyclerview.widget.RecyclerView; 42 43 import com.android.car.apps.common.BackgroundImageView; 44 import com.android.car.apps.common.imaging.ImageBinder; 45 import com.android.car.apps.common.imaging.ImageBinder.PlaceholderType; 46 import com.android.car.apps.common.imaging.ImageViewBinder; 47 import com.android.car.apps.common.util.ViewUtils; 48 import com.android.car.media.common.MediaItemMetadata; 49 import com.android.car.media.common.MetadataController; 50 import com.android.car.media.common.PlaybackControlsActionBar; 51 import com.android.car.media.common.playback.PlaybackViewModel; 52 import com.android.car.media.common.source.MediaSourceViewModel; 53 54 import java.util.ArrayList; 55 import java.util.Collections; 56 import java.util.List; 57 import java.util.Objects; 58 59 60 /** 61 * A {@link Fragment} that implements both the playback and the content forward browsing experience. 62 * It observes a {@link PlaybackViewModel} and updates its information depending on the currently 63 * playing media source through the {@link android.media.session.MediaSession} API. 64 */ 65 public class PlaybackFragment extends Fragment { 66 private static final String TAG = "PlaybackFragment"; 67 68 private ImageBinder<MediaItemMetadata.ArtworkRef> mAlbumArtBinder; 69 private BackgroundImageView mAlbumBackground; 70 private View mBackgroundScrim; 71 private View mControlBarScrim; 72 private PlaybackControlsActionBar mPlaybackControls; 73 private QueueItemsAdapter mQueueAdapter; 74 private RecyclerView mQueue; 75 private ViewGroup mSeekBarContainer; 76 private SeekBar mSeekBar; 77 private View mQueueButton; 78 private ViewGroup mNavIconContainer; 79 private List<View> mViewsToHideForCustomActions; 80 private List<View> mViewsToHideWhenQueueIsVisible; 81 private List<View> mViewsToShowWhenQueueIsVisible; 82 private List<View> mViewsToHideImmediatelyWhenQueueIsVisible; 83 private List<View> mViewsToShowImmediatelyWhenQueueIsVisible; 84 85 private DefaultItemAnimator mItemAnimator; 86 87 private MetadataController mMetadataController; 88 89 private PlaybackFragmentListener mListener; 90 91 private PlaybackViewModel.PlaybackController mController; 92 private Long mActiveQueueItemId; 93 94 private boolean mHasQueue; 95 private boolean mQueueIsVisible; 96 private boolean mShowTimeForActiveQueueItem; 97 private boolean mShowIconForActiveQueueItem; 98 private boolean mShowThumbnailForQueueItem; 99 private boolean mShowSubtitleForQueueItem; 100 101 private boolean mShowLinearProgressBar; 102 103 private int mFadeDuration; 104 105 private MediaActivity.ViewModel mViewModel; 106 107 /** 108 * PlaybackFragment listener 109 */ 110 public interface PlaybackFragmentListener { 111 /** 112 * Invoked when the user clicks on the collapse button 113 */ onCollapse()114 void onCollapse(); 115 } 116 117 public class QueueViewHolder extends RecyclerView.ViewHolder { 118 119 private final View mView; 120 private final ViewGroup mThumbnailContainer; 121 private final ImageView mThumbnail; 122 private final View mSpacer; 123 private final TextView mTitle; 124 private final TextView mSubtitle; 125 private final TextView mCurrentTime; 126 private final TextView mMaxTime; 127 private final TextView mTimeSeparator; 128 private final ImageView mActiveIcon; 129 130 private final ImageViewBinder<MediaItemMetadata.ArtworkRef> mThumbnailBinder; 131 QueueViewHolder(View itemView)132 QueueViewHolder(View itemView) { 133 super(itemView); 134 mView = itemView; 135 mThumbnailContainer = itemView.findViewById(R.id.thumbnail_container); 136 mThumbnail = itemView.findViewById(R.id.thumbnail); 137 mSpacer = itemView.findViewById(R.id.spacer); 138 mTitle = itemView.findViewById(R.id.queue_list_item_title); 139 mSubtitle = itemView.findViewById(R.id.queue_list_item_subtitle); 140 mCurrentTime = itemView.findViewById(R.id.current_time); 141 mMaxTime = itemView.findViewById(R.id.max_time); 142 mTimeSeparator = itemView.findViewById(R.id.separator); 143 mActiveIcon = itemView.findViewById(R.id.now_playing_icon); 144 145 Size maxArtSize = MediaAppConfig.getMediaItemsBitmapMaxSize(itemView.getContext()); 146 mThumbnailBinder = new ImageViewBinder<>(maxArtSize, mThumbnail); 147 } 148 bind(MediaItemMetadata item)149 void bind(MediaItemMetadata item) { 150 mView.setOnClickListener(v -> onQueueItemClicked(item)); 151 152 ViewUtils.setVisible(mThumbnailContainer, mShowThumbnailForQueueItem); 153 if (mShowThumbnailForQueueItem) { 154 Context context = mView.getContext(); 155 mThumbnailBinder.setImage(context, item != null ? item.getArtworkKey() : null); 156 } 157 158 ViewUtils.setVisible(mSpacer, !mShowThumbnailForQueueItem); 159 160 mTitle.setText(item.getTitle()); 161 162 boolean active = mActiveQueueItemId != null && Objects.equals(mActiveQueueItemId, 163 item.getQueueId()); 164 if (active) { 165 mCurrentTime.setText(mQueueAdapter.getCurrentTime()); 166 mMaxTime.setText(mQueueAdapter.getMaxTime()); 167 } 168 boolean shouldShowTime = 169 mShowTimeForActiveQueueItem && active && mQueueAdapter.getTimeVisible(); 170 ViewUtils.setVisible(mCurrentTime, shouldShowTime); 171 ViewUtils.setVisible(mMaxTime, shouldShowTime); 172 ViewUtils.setVisible(mTimeSeparator, shouldShowTime); 173 174 boolean shouldShowIcon = mShowIconForActiveQueueItem && active; 175 ViewUtils.setVisible(mActiveIcon, shouldShowIcon); 176 177 if (mShowSubtitleForQueueItem) { 178 mSubtitle.setText(item.getSubtitle()); 179 } 180 } 181 onViewAttachedToWindow()182 void onViewAttachedToWindow() { 183 if (mShowThumbnailForQueueItem) { 184 Context context = mView.getContext(); 185 mThumbnailBinder.maybeRestartLoading(context); 186 } 187 } 188 onViewDetachedFromWindow()189 void onViewDetachedFromWindow() { 190 if (mShowThumbnailForQueueItem) { 191 Context context = mView.getContext(); 192 mThumbnailBinder.maybeCancelLoading(context); 193 } 194 } 195 } 196 197 198 private class QueueItemsAdapter extends RecyclerView.Adapter<QueueViewHolder> { 199 200 private List<MediaItemMetadata> mQueueItems = Collections.emptyList(); 201 private String mCurrentTimeText = ""; 202 private String mMaxTimeText = ""; 203 private Integer mActiveItemPos; 204 private boolean mTimeVisible; 205 setItems(@ullable List<MediaItemMetadata> items)206 void setItems(@Nullable List<MediaItemMetadata> items) { 207 List<MediaItemMetadata> newQueueItems = 208 new ArrayList<>(items != null ? items : Collections.emptyList()); 209 if (newQueueItems.equals(mQueueItems)) { 210 return; 211 } 212 mQueueItems = newQueueItems; 213 updateActiveItem(); 214 notifyDataSetChanged(); 215 } 216 217 // Updates mActiveItemPos, then scrolls the queue to mActiveItemPos. 218 // It should be called when the active item (mActiveQueueItemId) changed or 219 // the queue items (mQueueItems) changed. updateActiveItem()220 void updateActiveItem() { 221 if (mQueueItems == null || mActiveQueueItemId == null) { 222 mActiveItemPos = null; 223 return; 224 } 225 Integer activeItemPos = null; 226 for (int i = 0; i < mQueueItems.size(); i++) { 227 if (mQueueItems.get(i).getQueueId() == mActiveQueueItemId) { 228 activeItemPos = i; 229 break; 230 } 231 } 232 233 if (mActiveItemPos != activeItemPos) { 234 if (mActiveItemPos != null) { 235 notifyItemChanged(mActiveItemPos.intValue()); 236 } 237 mActiveItemPos = activeItemPos; 238 if (mActiveItemPos != null) { 239 mQueue.scrollToPosition(mActiveItemPos.intValue()); 240 notifyItemChanged(mActiveItemPos.intValue()); 241 } 242 } 243 } 244 setCurrentTime(String currentTime)245 void setCurrentTime(String currentTime) { 246 if (!mCurrentTimeText.equals(currentTime)) { 247 mCurrentTimeText = currentTime; 248 if (mActiveItemPos != null) { 249 notifyItemChanged(mActiveItemPos.intValue()); 250 } 251 } 252 } 253 setMaxTime(String maxTime)254 void setMaxTime(String maxTime) { 255 if (!mMaxTimeText.equals(maxTime)) { 256 mMaxTimeText = maxTime; 257 if (mActiveItemPos != null) { 258 notifyItemChanged(mActiveItemPos.intValue()); 259 } 260 } 261 } 262 setTimeVisible(boolean visible)263 void setTimeVisible(boolean visible) { 264 if (mTimeVisible != visible) { 265 mTimeVisible = visible; 266 if (mActiveItemPos != null) { 267 notifyItemChanged(mActiveItemPos.intValue()); 268 } 269 } 270 } 271 getCurrentTime()272 String getCurrentTime() { 273 return mCurrentTimeText; 274 } 275 getMaxTime()276 String getMaxTime() { 277 return mMaxTimeText; 278 } 279 getTimeVisible()280 boolean getTimeVisible() { 281 return mTimeVisible; 282 } 283 284 @Override onCreateViewHolder(ViewGroup parent, int viewType)285 public QueueViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 286 LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 287 return new QueueViewHolder(inflater.inflate(R.layout.queue_list_item, parent, false)); 288 } 289 290 @Override onBindViewHolder(QueueViewHolder holder, int position)291 public void onBindViewHolder(QueueViewHolder holder, int position) { 292 int size = mQueueItems.size(); 293 if (0 <= position && position < size) { 294 holder.bind(mQueueItems.get(position)); 295 } else { 296 Log.e(TAG, "onBindViewHolder invalid position " + position + " of " + size); 297 } 298 } 299 300 @Override onViewAttachedToWindow(@onNull QueueViewHolder holder)301 public void onViewAttachedToWindow(@NonNull QueueViewHolder holder) { 302 super.onViewAttachedToWindow(holder); 303 holder.onViewAttachedToWindow(); 304 } 305 306 @Override onViewDetachedFromWindow(@onNull QueueViewHolder holder)307 public void onViewDetachedFromWindow(@NonNull QueueViewHolder holder) { 308 super.onViewDetachedFromWindow(holder); 309 holder.onViewDetachedFromWindow(); 310 } 311 312 @Override getItemCount()313 public int getItemCount() { 314 return mQueueItems.size(); 315 } 316 refresh()317 void refresh() { 318 // TODO: Perform a diff between current and new content and trigger the proper 319 // RecyclerView updates. 320 this.notifyDataSetChanged(); 321 } 322 323 @Override getItemId(int position)324 public long getItemId(int position) { 325 return mQueueItems.get(position).getQueueId(); 326 } 327 } 328 329 private class QueueTopItemDecoration extends RecyclerView.ItemDecoration { 330 int mHeight; 331 int mDecorationPosition; 332 QueueTopItemDecoration(int height, int decorationPosition)333 QueueTopItemDecoration(int height, int decorationPosition) { 334 mHeight = height; 335 mDecorationPosition = decorationPosition; 336 } 337 338 @Override getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)339 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 340 RecyclerView.State state) { 341 super.getItemOffsets(outRect, view, parent, state); 342 if (parent.getChildAdapterPosition(view) == mDecorationPosition) { 343 outRect.top = mHeight; 344 } 345 } 346 } 347 348 @Override onCreateView(@onNull LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState)349 public View onCreateView(@NonNull LayoutInflater inflater, final ViewGroup container, 350 Bundle savedInstanceState) { 351 View view = inflater.inflate(R.layout.fragment_playback, container, false); 352 mAlbumBackground = view.findViewById(R.id.playback_background); 353 mQueue = view.findViewById(R.id.queue_list); 354 mSeekBarContainer = view.findViewById(R.id.seek_bar_container); 355 mSeekBar = view.findViewById(R.id.seek_bar); 356 mQueueButton = view.findViewById(R.id.queue_button); 357 mQueueButton.setOnClickListener(button -> toggleQueueVisibility()); 358 mNavIconContainer = view.findViewById(R.id.nav_icon_container); 359 mNavIconContainer.setOnClickListener(nav -> onCollapse()); 360 mBackgroundScrim = view.findViewById(R.id.background_scrim); 361 ViewUtils.setVisible(mBackgroundScrim, false); 362 mControlBarScrim = view.findViewById(R.id.control_bar_scrim); 363 if (mControlBarScrim != null) { 364 ViewUtils.setVisible(mControlBarScrim, false); 365 mControlBarScrim.setOnClickListener(scrim -> mPlaybackControls.close()); 366 mControlBarScrim.setClickable(false); 367 } 368 369 Resources res = getResources(); 370 mShowTimeForActiveQueueItem = res.getBoolean( 371 R.bool.show_time_for_now_playing_queue_list_item); 372 mShowIconForActiveQueueItem = res.getBoolean( 373 R.bool.show_icon_for_now_playing_queue_list_item); 374 mShowThumbnailForQueueItem = getContext().getResources().getBoolean( 375 R.bool.show_thumbnail_for_queue_list_item); 376 mShowLinearProgressBar = getContext().getResources().getBoolean( 377 R.bool.show_linear_progress_bar); 378 mShowSubtitleForQueueItem = getContext().getResources().getBoolean( 379 R.bool.show_subtitle_for_queue_list_item); 380 381 if (mSeekBar != null) { 382 if (mShowLinearProgressBar) { 383 boolean useMediaSourceColor = res.getBoolean( 384 R.bool.use_media_source_color_for_progress_bar); 385 int defaultColor = res.getColor(R.color.progress_bar_highlight, null); 386 if (useMediaSourceColor) { 387 getPlaybackViewModel().getMediaSourceColors().observe(getViewLifecycleOwner(), 388 sourceColors -> { 389 int color = sourceColors != null ? sourceColors.getAccentColor( 390 defaultColor) 391 : defaultColor; 392 mSeekBar.setThumbTintList(ColorStateList.valueOf(color)); 393 mSeekBar.setProgressTintList(ColorStateList.valueOf(color)); 394 }); 395 } else { 396 mSeekBar.setThumbTintList(ColorStateList.valueOf(defaultColor)); 397 mSeekBar.setProgressTintList(ColorStateList.valueOf(defaultColor)); 398 } 399 } else { 400 mSeekBar.setVisibility(View.GONE); 401 } 402 } 403 404 ImageView logoView = view.findViewById(R.id.car_ui_toolbar_title_logo); 405 MediaSourceViewModel mediaSourceViewModel = getMediaSourceViewModel(); 406 mediaSourceViewModel.getPrimaryMediaSource().observe(this, mediaSource -> 407 logoView.setImageBitmap(mediaSource != null 408 ? mediaSource.getRoundPackageIcon() : null)); 409 410 mViewModel = ViewModelProviders.of(requireActivity()).get(MediaActivity.ViewModel.class); 411 412 getPlaybackViewModel().getPlaybackController().observe(getViewLifecycleOwner(), 413 controller -> mController = controller); 414 initPlaybackControls(view.findViewById(R.id.playback_controls)); 415 initMetadataController(view); 416 initQueue(); 417 418 // Don't update the visibility of seekBar if show_linear_progress_bar is false. 419 ViewUtils.Filter ignoreSeekBarFilter = 420 (viewToFilter) -> mShowLinearProgressBar || viewToFilter != mSeekBarContainer; 421 422 mViewsToHideForCustomActions = ViewUtils.getViewsById(view, res, 423 R.array.playback_views_to_hide_when_showing_custom_actions, ignoreSeekBarFilter); 424 mViewsToHideWhenQueueIsVisible = ViewUtils.getViewsById(view, res, 425 R.array.playback_views_to_hide_when_queue_is_visible, ignoreSeekBarFilter); 426 mViewsToShowWhenQueueIsVisible = ViewUtils.getViewsById(view, res, 427 R.array.playback_views_to_show_when_queue_is_visible, null); 428 mViewsToHideImmediatelyWhenQueueIsVisible = ViewUtils.getViewsById(view, res, 429 R.array.playback_views_to_hide_immediately_when_queue_is_visible, ignoreSeekBarFilter); 430 mViewsToShowImmediatelyWhenQueueIsVisible = ViewUtils.getViewsById(view, res, 431 R.array.playback_views_to_show_immediately_when_queue_is_visible, null); 432 433 mAlbumArtBinder = new ImageBinder<>( 434 PlaceholderType.BACKGROUND, 435 MediaAppConfig.getMediaItemsBitmapMaxSize(getContext()), 436 drawable -> mAlbumBackground.setBackgroundDrawable(drawable)); 437 438 getPlaybackViewModel().getMetadata().observe(getViewLifecycleOwner(), 439 item -> mAlbumArtBinder.setImage(PlaybackFragment.this.getContext(), 440 item != null ? item.getArtworkKey() : null)); 441 442 return view; 443 } 444 445 @Override onAttach(Context context)446 public void onAttach(Context context) { 447 super.onAttach(context); 448 } 449 450 @Override onDetach()451 public void onDetach() { 452 super.onDetach(); 453 } 454 initPlaybackControls(PlaybackControlsActionBar playbackControls)455 private void initPlaybackControls(PlaybackControlsActionBar playbackControls) { 456 mPlaybackControls = playbackControls; 457 mPlaybackControls.setModel(getPlaybackViewModel(), getViewLifecycleOwner()); 458 mPlaybackControls.registerExpandCollapseCallback((expanding) -> { 459 Resources res = getContext().getResources(); 460 int millis = expanding ? res.getInteger(R.integer.control_bar_expand_anim_duration) : 461 res.getInteger(R.integer.control_bar_collapse_anim_duration); 462 463 if (mControlBarScrim != null) { 464 mControlBarScrim.setClickable(expanding); 465 } 466 467 if (expanding) { 468 if (mControlBarScrim != null) { 469 ViewUtils.showViewAnimated(mControlBarScrim, millis); 470 } 471 } else { 472 if (mControlBarScrim != null) { 473 ViewUtils.hideViewAnimated(mControlBarScrim, millis); 474 } 475 } 476 477 if (!mQueueIsVisible) { 478 for (View view : mViewsToHideForCustomActions) { 479 if (expanding) { 480 ViewUtils.hideViewAnimated(view, millis); 481 } else { 482 ViewUtils.showViewAnimated(view, millis); 483 } 484 } 485 } 486 }); 487 } 488 initQueue()489 private void initQueue() { 490 mFadeDuration = getResources().getInteger( 491 R.integer.fragment_playback_queue_fade_duration_ms); 492 493 int decorationHeight = getResources().getDimensionPixelSize( 494 R.dimen.playback_queue_list_padding_top); 495 // Put the decoration above the first item. 496 int decorationPosition = 0; 497 mQueue.addItemDecoration(new QueueTopItemDecoration(decorationHeight, decorationPosition)); 498 499 mQueue.setVerticalFadingEdgeEnabled( 500 getResources().getBoolean(R.bool.queue_fading_edge_length_enabled)); 501 mQueueAdapter = new QueueItemsAdapter(); 502 503 getPlaybackViewModel().getPlaybackStateWrapper().observe(getViewLifecycleOwner(), 504 state -> { 505 Long itemId = (state != null) ? state.getActiveQueueItemId() : null; 506 if (!Objects.equals(mActiveQueueItemId, itemId)) { 507 mActiveQueueItemId = itemId; 508 mQueueAdapter.updateActiveItem(); 509 } 510 }); 511 mQueue.setAdapter(mQueueAdapter); 512 513 // Disable item changed animation. 514 mItemAnimator = new DefaultItemAnimator(); 515 mItemAnimator.setSupportsChangeAnimations(false); 516 mQueue.setItemAnimator(mItemAnimator); 517 518 getPlaybackViewModel().getQueue().observe(this, this::setQueue); 519 520 getPlaybackViewModel().hasQueue().observe(getViewLifecycleOwner(), hasQueue -> { 521 boolean enableQueue = (hasQueue != null) && hasQueue; 522 mQueueIsVisible = mViewModel.getQueueVisible(); 523 setHasQueue(enableQueue); 524 }); 525 getPlaybackViewModel().getProgress().observe(getViewLifecycleOwner(), 526 playbackProgress -> 527 { 528 mQueueAdapter.setCurrentTime(playbackProgress.getCurrentTimeText().toString()); 529 mQueueAdapter.setMaxTime(playbackProgress.getMaxTimeText().toString()); 530 mQueueAdapter.setTimeVisible(playbackProgress.hasTime()); 531 }); 532 } 533 setQueue(List<MediaItemMetadata> queueItems)534 private void setQueue(List<MediaItemMetadata> queueItems) { 535 mQueueAdapter.setItems(queueItems); 536 } 537 initMetadataController(View view)538 private void initMetadataController(View view) { 539 ImageView albumArt = view.findViewById(R.id.album_art); 540 TextView title = view.findViewById(R.id.title); 541 TextView artist = view.findViewById(R.id.artist); 542 TextView albumTitle = view.findViewById(R.id.album_title); 543 TextView outerSeparator = view.findViewById(R.id.outer_separator); 544 TextView curTime = view.findViewById(R.id.current_time); 545 TextView innerSeparator = view.findViewById(R.id.inner_separator); 546 TextView maxTime = view.findViewById(R.id.max_time); 547 SeekBar seekbar = mShowLinearProgressBar ? mSeekBar : null; 548 549 Size maxArtSize = MediaAppConfig.getMediaItemsBitmapMaxSize(view.getContext()); 550 mMetadataController = new MetadataController(getViewLifecycleOwner(), 551 getPlaybackViewModel(), title, artist, albumTitle, outerSeparator, 552 curTime, innerSeparator, maxTime, seekbar, albumArt, maxArtSize); 553 } 554 555 /** 556 * Hides or shows the playback queue when the user clicks the queue button. 557 */ toggleQueueVisibility()558 private void toggleQueueVisibility() { 559 boolean updatedQueueVisibility = !mQueueIsVisible; 560 setQueueVisible(updatedQueueVisibility); 561 562 // When the visibility of queue is changed by the user, save the visibility into ViewModel 563 // so that we can restore PlaybackFragment properly when needed. If it's changed by media 564 // source change (media source changes -> hasQueue becomes false -> queue is hidden), don't 565 // save it. 566 mViewModel.setQueueVisible(updatedQueueVisibility); 567 } 568 setQueueVisible(boolean visible)569 private void setQueueVisible(boolean visible) { 570 mQueueIsVisible = visible; 571 mQueueButton.setActivated(mQueueIsVisible); 572 mQueueButton.setSelected(mQueueIsVisible); 573 if (mQueueIsVisible) { 574 ViewUtils.showViewsAnimated(mViewsToShowWhenQueueIsVisible, mFadeDuration); 575 ViewUtils.hideViewsAnimated(mViewsToHideWhenQueueIsVisible, mFadeDuration); 576 ViewUtils.setVisible(mViewsToShowImmediatelyWhenQueueIsVisible, true); 577 ViewUtils.setVisible(mViewsToHideImmediatelyWhenQueueIsVisible, false); 578 } else { 579 ViewUtils.hideViewsAnimated(mViewsToShowWhenQueueIsVisible, mFadeDuration); 580 ViewUtils.showViewsAnimated(mViewsToHideWhenQueueIsVisible, mFadeDuration); 581 ViewUtils.setVisible(mViewsToShowImmediatelyWhenQueueIsVisible, false); 582 ViewUtils.setVisible(mViewsToHideImmediatelyWhenQueueIsVisible, true); 583 } 584 } 585 586 /** Sets whether the source has a queue. */ setHasQueue(boolean hasQueue)587 private void setHasQueue(boolean hasQueue) { 588 mHasQueue = hasQueue; 589 mQueueButton.setVisibility(mHasQueue ? View.VISIBLE : View.GONE); 590 setQueueVisible(hasQueue && mQueueIsVisible); 591 } 592 onQueueItemClicked(MediaItemMetadata item)593 private void onQueueItemClicked(MediaItemMetadata item) { 594 if (mController != null) { 595 mController.skipToQueueItem(item.getQueueId()); 596 } 597 boolean switchToPlayback = getResources().getBoolean( 598 R.bool.switch_to_playback_view_when_playable_item_is_clicked); 599 if (switchToPlayback) { 600 toggleQueueVisibility(); 601 } 602 } 603 604 /** 605 * Collapses the playback controls. 606 */ closeOverflowMenu()607 public void closeOverflowMenu() { 608 mPlaybackControls.close(); 609 } 610 getPlaybackViewModel()611 private PlaybackViewModel getPlaybackViewModel() { 612 return PlaybackViewModel.get(getActivity().getApplication()); 613 } 614 getMediaSourceViewModel()615 private MediaSourceViewModel getMediaSourceViewModel() { 616 return MediaSourceViewModel.get(getActivity().getApplication()); 617 } 618 619 /** 620 * Sets a listener of this PlaybackFragment events. In order to avoid memory leaks, consumers 621 * must reset this reference by setting the listener to null. 622 */ setListener(PlaybackFragmentListener listener)623 public void setListener(PlaybackFragmentListener listener) { 624 mListener = listener; 625 } 626 onCollapse()627 private void onCollapse() { 628 if (mListener != null) { 629 mListener.onCollapse(); 630 } 631 } 632 } 633