1 /*
2  * Copyright (C) 2015 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.tv.menu;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.text.TextUtils;
22 import android.text.format.DateFormat;
23 import android.util.AttributeSet;
24 import android.view.View;
25 import android.widget.TextView;
26 import android.widget.Toast;
27 
28 import com.android.tv.MainActivity;
29 import com.android.tv.R;
30 import com.android.tv.TimeShiftManager;
31 import com.android.tv.TimeShiftManager.TimeShiftActionId;
32 import com.android.tv.TvSingletons;
33 import com.android.tv.common.SoftPreconditions;
34 import com.android.tv.common.feature.CommonFeatures;
35 import com.android.tv.data.api.Channel;
36 import com.android.tv.data.api.Program;
37 import com.android.tv.dialog.HalfSizedDialogFragment;
38 import com.android.tv.dvr.DvrDataManager;
39 import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener;
40 import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener;
41 import com.android.tv.dvr.DvrManager;
42 import com.android.tv.dvr.data.ScheduledRecording;
43 import com.android.tv.dvr.ui.DvrStopRecordingFragment;
44 import com.android.tv.dvr.ui.DvrUiHelper;
45 import com.android.tv.menu.Menu.MenuShowReason;
46 import com.android.tv.ui.TunableTvView;
47 
48 public class PlayControlsRowView extends MenuRowView {
49     private static final int NORMAL_WIDTH_MAX_BUTTON_COUNT = 5;
50     // Dimensions
51     private final int mTimeIndicatorLeftMargin;
52     private final int mTimeTextLeftMargin;
53     private final int mTimelineWidth;
54     // Views
55     private TextView mBackgroundView;
56     private View mTimeIndicator;
57     private TextView mTimeText;
58     private PlaybackProgressBar mProgress;
59     private PlayControlsButton mJumpPreviousButton;
60     private PlayControlsButton mRewindButton;
61     private PlayControlsButton mPlayPauseButton;
62     private PlayControlsButton mFastForwardButton;
63     private PlayControlsButton mJumpNextButton;
64     private PlayControlsButton mRecordButton;
65     private TextView mProgramStartTimeText;
66     private TextView mProgramEndTimeText;
67     private TunableTvView mTvView;
68     private TimeShiftManager mTimeShiftManager;
69     private final DvrDataManager mDvrDataManager;
70     private final DvrManager mDvrManager;
71     private final MainActivity mMainActivity;
72 
73     private final java.text.DateFormat mTimeFormat;
74     private long mProgramStartTimeMs;
75     private long mProgramEndTimeMs;
76     private boolean mUseCompactLayout;
77     private final int mNormalButtonMargin;
78     private final int mCompactButtonMargin;
79 
80     private final String mUnavailableMessage;
81 
82     private final ScheduledRecordingListener mScheduledRecordingListener =
83             new ScheduledRecordingListener() {
84                 @Override
85                 public void onScheduledRecordingAdded(ScheduledRecording... scheduledRecordings) {}
86 
87                 @Override
88                 public void onScheduledRecordingRemoved(
89                         ScheduledRecording... scheduledRecordings) {}
90 
91                 @Override
92                 public void onScheduledRecordingStatusChanged(
93                         ScheduledRecording... scheduledRecordings) {
94                     Channel currentChannel = mMainActivity.getCurrentChannel();
95                     if (currentChannel != null && isShown()) {
96                         for (ScheduledRecording schedule : scheduledRecordings) {
97                             if (schedule.getChannelId() == currentChannel.getId()) {
98                                 updateRecordButton();
99                                 break;
100                             }
101                         }
102                     }
103                 }
104             };
105 
PlayControlsRowView(Context context)106     public PlayControlsRowView(Context context) {
107         this(context, null);
108     }
109 
PlayControlsRowView(Context context, AttributeSet attrs)110     public PlayControlsRowView(Context context, AttributeSet attrs) {
111         this(context, attrs, 0);
112     }
113 
PlayControlsRowView(Context context, AttributeSet attrs, int defStyleAttr)114     public PlayControlsRowView(Context context, AttributeSet attrs, int defStyleAttr) {
115         this(context, attrs, defStyleAttr, 0);
116     }
117 
PlayControlsRowView( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)118     public PlayControlsRowView(
119             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
120         super(context, attrs, defStyleAttr, defStyleRes);
121         Resources res = context.getResources();
122         mTimeIndicatorLeftMargin =
123                 -res.getDimensionPixelSize(R.dimen.play_controls_time_indicator_width) / 2;
124         mTimeTextLeftMargin = -res.getDimensionPixelOffset(R.dimen.play_controls_time_width) / 2;
125         mTimelineWidth = res.getDimensionPixelSize(R.dimen.play_controls_width);
126         mTimeFormat = DateFormat.getTimeFormat(context);
127         mNormalButtonMargin = res.getDimensionPixelSize(R.dimen.play_controls_button_normal_margin);
128         mCompactButtonMargin =
129                 res.getDimensionPixelSize(R.dimen.play_controls_button_compact_margin);
130         if (CommonFeatures.DVR.isEnabled(context)) {
131             mDvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager();
132             mDvrManager = TvSingletons.getSingletons(context).getDvrManager();
133         } else {
134             mDvrDataManager = null;
135             mDvrManager = null;
136         }
137         mMainActivity = (MainActivity) context;
138         mUnavailableMessage = res.getString(R.string.play_controls_unavailable);
139     }
140 
141     @Override
onAttachedToWindow()142     public void onAttachedToWindow() {
143         super.onAttachedToWindow();
144         if (mDvrDataManager != null) {
145             mDvrDataManager.addScheduledRecordingListener(mScheduledRecordingListener);
146             if (!mDvrDataManager.isDvrScheduleLoadFinished()) {
147                 mDvrDataManager.addDvrScheduleLoadFinishedListener(
148                         new OnDvrScheduleLoadFinishedListener() {
149                             @Override
150                             public void onDvrScheduleLoadFinished() {
151                                 mDvrDataManager.removeDvrScheduleLoadFinishedListener(this);
152                                 if (isShown()) {
153                                     updateRecordButton();
154                                 }
155                             }
156                         });
157             }
158         }
159     }
160 
161     @Override
getContentsViewId()162     protected int getContentsViewId() {
163         return R.id.play_controls;
164     }
165 
166     @Override
onFinishInflate()167     protected void onFinishInflate() {
168         super.onFinishInflate();
169         // Clip the ViewGroup(body) to the rounded rectangle of outline.
170         findViewById(R.id.body).setClipToOutline(true);
171         mBackgroundView = (TextView) findViewById(R.id.background);
172         mTimeIndicator = findViewById(R.id.time_indicator);
173         mTimeText = (TextView) findViewById(R.id.time_text);
174         mProgress = (PlaybackProgressBar) findViewById(R.id.progress);
175         mJumpPreviousButton = (PlayControlsButton) findViewById(R.id.jump_previous);
176         mRewindButton = (PlayControlsButton) findViewById(R.id.rewind);
177         mPlayPauseButton = (PlayControlsButton) findViewById(R.id.play_pause);
178         mFastForwardButton = (PlayControlsButton) findViewById(R.id.fast_forward);
179         mJumpNextButton = (PlayControlsButton) findViewById(R.id.jump_next);
180         mRecordButton = (PlayControlsButton) findViewById(R.id.record);
181         mProgramStartTimeText = (TextView) findViewById(R.id.program_start_time);
182         mProgramEndTimeText = (TextView) findViewById(R.id.program_end_time);
183 
184         initializeButton(
185                 mJumpPreviousButton,
186                 R.drawable.lb_ic_skip_previous,
187                 R.string.play_controls_description_skip_previous,
188                 null,
189                 () -> {
190                     if (mTimeShiftManager.isAvailable()) {
191                         mTimeShiftManager.jumpToPrevious();
192                         updateControls(true);
193                     }
194                 });
195         initializeButton(
196                 mRewindButton,
197                 R.drawable.lb_ic_fast_rewind,
198                 R.string.play_controls_description_fast_rewind,
199                 null,
200                 () -> {
201                     if (mTimeShiftManager.isAvailable()) {
202                         mTimeShiftManager.rewind();
203                         updateButtons();
204                     }
205                 });
206         initializeButton(
207                 mPlayPauseButton,
208                 R.drawable.lb_ic_play,
209                 R.string.play_controls_description_play_pause,
210                 null,
211                 () -> {
212                     if (mTimeShiftManager.isAvailable()) {
213                         mTimeShiftManager.togglePlayPause();
214                         updateButtons();
215                     }
216                 });
217         initializeButton(
218                 mFastForwardButton,
219                 R.drawable.lb_ic_fast_forward,
220                 R.string.play_controls_description_fast_forward,
221                 null,
222                 () -> {
223                     if (mTimeShiftManager.isAvailable()) {
224                         mTimeShiftManager.fastForward();
225                         updateButtons();
226                     }
227                 });
228         initializeButton(
229                 mJumpNextButton,
230                 R.drawable.lb_ic_skip_next,
231                 R.string.play_controls_description_skip_next,
232                 null,
233                 () -> {
234                     if (mTimeShiftManager.isAvailable()) {
235                         mTimeShiftManager.jumpToNext();
236                         updateControls(true);
237                     }
238                 });
239         int color =
240                 getResources().getColor(R.color.play_controls_recording_icon_color_on_focus, null);
241         initializeButton(
242                 mRecordButton,
243                 R.drawable.ic_record_start,
244                 R.string.channels_item_record_start,
245                 color,
246                 this::onRecordButtonClicked);
247     }
248 
isCurrentChannelRecording()249     private boolean isCurrentChannelRecording() {
250         Channel currentChannel = mMainActivity.getCurrentChannel();
251         return currentChannel != null
252                 && mDvrManager != null
253                 && mDvrManager.getCurrentRecording(currentChannel.getId()) != null;
254     }
255 
onRecordButtonClicked()256     private void onRecordButtonClicked() {
257         boolean isRecording = isCurrentChannelRecording();
258         Channel currentChannel = mMainActivity.getCurrentChannel();
259         TvSingletons.getSingletons(getContext())
260                 .getTracker()
261                 .sendMenuClicked(
262                         isRecording
263                                 ? R.string.channels_item_record_start
264                                 : R.string.channels_item_record_stop);
265         if (!isRecording) {
266             if (!(mDvrManager != null && mDvrManager.isChannelRecordable(currentChannel))) {
267                 Toast.makeText(
268                                 mMainActivity,
269                                 R.string.dvr_msg_cannot_record_channel,
270                                 Toast.LENGTH_SHORT)
271                         .show();
272             } else {
273                 Program program =
274                         TvSingletons.getSingletons(mMainActivity)
275                                 .getProgramDataManager()
276                                 .getCurrentProgram(currentChannel.getId());
277                 DvrUiHelper.checkStorageStatusAndShowErrorMessage(
278                         mMainActivity,
279                         currentChannel.getInputId(),
280                         () ->
281                                 DvrUiHelper.requestRecordingCurrentProgram(
282                                         mMainActivity, currentChannel, program, true));
283             }
284         } else if (currentChannel != null) {
285             DvrUiHelper.showStopRecordingDialog(
286                     mMainActivity,
287                     currentChannel.getId(),
288                     DvrStopRecordingFragment.REASON_USER_STOP,
289                     new HalfSizedDialogFragment.OnActionClickListener() {
290                         @Override
291                         public void onActionClick(long actionId) {
292                             if (actionId == DvrStopRecordingFragment.ACTION_STOP) {
293                                 ScheduledRecording currentRecording =
294                                         mDvrManager.getCurrentRecording(currentChannel.getId());
295                                 if (currentRecording != null) {
296                                     mDvrManager.stopRecording(currentRecording);
297                                 }
298                             }
299                         }
300                     });
301         }
302     }
303 
initializeButton( PlayControlsButton button, int imageResId, int descriptionId, Integer focusedIconColor, Runnable clickAction)304     private void initializeButton(
305             PlayControlsButton button,
306             int imageResId,
307             int descriptionId,
308             Integer focusedIconColor,
309             Runnable clickAction) {
310         button.setImageResId(imageResId);
311         button.setAction(clickAction);
312         if (focusedIconColor != null) {
313             button.setFocusedIconColor(focusedIconColor);
314         }
315         button.findViewById(R.id.button)
316                 .setContentDescription(getResources().getString(descriptionId));
317     }
318 
319     @Override
onBind(MenuRow row)320     public void onBind(MenuRow row) {
321         super.onBind(row);
322         PlayControlsRow playControlsRow = (PlayControlsRow) row;
323         mTvView = playControlsRow.getTvView();
324         mTimeShiftManager = playControlsRow.getTimeShiftManager();
325         mTimeShiftManager.setListener(
326                 new TimeShiftManager.Listener() {
327                     @Override
328                     public void onAvailabilityChanged() {
329                         updateMenuVisibility();
330                         PlayControlsRowView.this.updateAll(false);
331                     }
332 
333                     @Override
334                     public void onPlayStatusChanged(int status) {
335                         updateMenuVisibility();
336                         if (mTimeShiftManager.isAvailable()) {
337                             updateControls(false);
338                         }
339                     }
340 
341                     @Override
342                     public void onRecordTimeRangeChanged() {
343                         if (mTimeShiftManager.isAvailable()) {
344                             updateControls(false);
345                         }
346                     }
347 
348                     @Override
349                     public void onCurrentPositionChanged() {
350                         if (mTimeShiftManager.isAvailable()) {
351                             initializeTimeline();
352                             updateControls(false);
353                         }
354                     }
355 
356                     @Override
357                     public void onProgramInfoChanged() {
358                         if (mTimeShiftManager.isAvailable()) {
359                             initializeTimeline();
360                             updateControls(false);
361                         }
362                     }
363 
364                     @Override
365                     public void onActionEnabledChanged(
366                             @TimeShiftActionId int actionId, boolean enabled) {
367                         // Move focus to the play/pause button when the PREVIOUS, NEXT, REWIND or
368                         // FAST_FORWARD button is clicked and the button becomes disabled.
369                         // No need to update the UI here because the UI will be updated by other
370                         // callbacks.
371                         if (!enabled
372                                 && ((actionId
373                                                         == TimeShiftManager
374                                                                 .TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS
375                                                 && mJumpPreviousButton.hasFocus())
376                                         || (actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND
377                                                 && mRewindButton.hasFocus())
378                                         || (actionId
379                                                         == TimeShiftManager
380                                                                 .TIME_SHIFT_ACTION_ID_FAST_FORWARD
381                                                 && mFastForwardButton.hasFocus())
382                                         || (actionId
383                                                         == TimeShiftManager
384                                                                 .TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT
385                                                 && mJumpNextButton.hasFocus()))) {
386                             mPlayPauseButton.requestFocus();
387                         }
388                     }
389                 });
390         // force update to initialize everything
391         updateAll(true);
392     }
393 
initializeTimeline()394     private void initializeTimeline() {
395         Program program = mTimeShiftManager.getProgramAt(mTimeShiftManager.getCurrentPositionMs());
396         mProgramStartTimeMs = program.getStartTimeUtcMillis();
397         mProgramEndTimeMs = program.getEndTimeUtcMillis();
398         mProgress.setMax(mProgramEndTimeMs - mProgramStartTimeMs);
399         updateRecTimeText();
400         SoftPreconditions.checkArgument(mProgramStartTimeMs <= mProgramEndTimeMs);
401     }
402 
updateMenuVisibility()403     private void updateMenuVisibility() {
404         boolean keepMenuVisible =
405                 mTimeShiftManager.isAvailable() && !mTimeShiftManager.isNormalPlaying();
406         getMenu().setKeepVisible(keepMenuVisible);
407     }
408 
onPreselected()409     public void onPreselected() {
410         updateControls(true);
411     }
412 
413     @Override
onSelected(boolean showTitle)414     public void onSelected(boolean showTitle) {
415         super.onSelected(showTitle);
416         postHideRippleAnimation();
417     }
418 
419     @Override
initialize(@enuShowReason int reason)420     public void initialize(@MenuShowReason int reason) {
421         super.initialize(reason);
422         switch (reason) {
423             case Menu.REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS:
424                 if (mTimeShiftManager.isActionEnabled(
425                         TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS)) {
426                     setInitialFocusView(mJumpPreviousButton);
427                 } else {
428                     setInitialFocusView(mPlayPauseButton);
429                 }
430                 break;
431             case Menu.REASON_PLAY_CONTROLS_REWIND:
432                 if (mTimeShiftManager.isActionEnabled(
433                         TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND)) {
434                     setInitialFocusView(mRewindButton);
435                 } else {
436                     setInitialFocusView(mPlayPauseButton);
437                 }
438                 break;
439             case Menu.REASON_PLAY_CONTROLS_FAST_FORWARD:
440                 if (mTimeShiftManager.isActionEnabled(
441                         TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD)) {
442                     setInitialFocusView(mFastForwardButton);
443                 } else {
444                     setInitialFocusView(mPlayPauseButton);
445                 }
446                 break;
447             case Menu.REASON_PLAY_CONTROLS_JUMP_TO_NEXT:
448                 if (mTimeShiftManager.isActionEnabled(
449                         TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT)) {
450                     setInitialFocusView(mJumpNextButton);
451                 } else {
452                     setInitialFocusView(mPlayPauseButton);
453                 }
454                 break;
455             case Menu.REASON_PLAY_CONTROLS_PLAY_PAUSE:
456             case Menu.REASON_PLAY_CONTROLS_PLAY:
457             case Menu.REASON_PLAY_CONTROLS_PAUSE:
458             default:
459                 setInitialFocusView(mPlayPauseButton);
460                 break;
461         }
462         postHideRippleAnimation();
463     }
464 
postHideRippleAnimation()465     private void postHideRippleAnimation() {
466         // Focus may be changed in another message if requestFocus is called in this message.
467         // After the focus is actually changed, hideRippleAnimation should run
468         // to reflect the result of the focus change. To be sure, hideRippleAnimation is posted.
469         post(
470                 () -> {
471                     mJumpPreviousButton.hideRippleAnimation();
472                     mRewindButton.hideRippleAnimation();
473                     mPlayPauseButton.hideRippleAnimation();
474                     mFastForwardButton.hideRippleAnimation();
475                     mJumpNextButton.hideRippleAnimation();
476                 });
477     }
478 
479     @Override
onChildFocusChange(View v, boolean hasFocus)480     protected void onChildFocusChange(View v, boolean hasFocus) {
481         super.onChildFocusChange(v, hasFocus);
482         if ((v.getParent().equals(mRewindButton) || v.getParent().equals(mFastForwardButton))
483                 && !hasFocus) {
484             if (mTimeShiftManager.getPlayStatus() == TimeShiftManager.PLAY_STATUS_PLAYING) {
485                 mTimeShiftManager.play();
486                 updateButtons();
487             }
488         }
489     }
490 
491     @Override
requestChildFocus()492     protected void requestChildFocus() {
493         mPlayPauseButton.requestFocusWithAccessibility();
494     }
495 
496     /** Updates the view contents. It is called from the PlayControlsRow. */
update()497     public void update() {
498         updateAll(false);
499     }
500 
updateAll(boolean forceUpdate)501     private void updateAll(boolean forceUpdate) {
502         if (mTimeShiftManager.isAvailable() && !mTvView.isScreenBlocked()) {
503             setEnabled(true);
504             initializeTimeline();
505             mBackgroundView.setEnabled(true);
506             setTextIfNeeded(mBackgroundView, null);
507         } else {
508             setEnabled(false);
509             mBackgroundView.setEnabled(false);
510             setTextIfNeeded(mBackgroundView, mUnavailableMessage);
511         }
512         // force the controls be updated no matter it's visible or not.
513         updateControls(forceUpdate);
514     }
515 
updateControls(boolean forceUpdate)516     private void updateControls(boolean forceUpdate) {
517         if (forceUpdate || getContentsView().isShown()) {
518             updateTime();
519             updateProgress();
520             updateButtons();
521             updateRecordButton();
522             updateButtonMargin();
523         }
524     }
525 
updateTime()526     private void updateTime() {
527         if (isEnabled()) {
528             mTimeText.setVisibility(View.VISIBLE);
529             mTimeIndicator.setVisibility(View.VISIBLE);
530         } else {
531             mTimeText.setVisibility(View.INVISIBLE);
532             mTimeIndicator.setVisibility(View.GONE);
533             return;
534         }
535         long currentPositionMs = mTimeShiftManager.getCurrentPositionMs();
536         int currentTimePositionPixel =
537                 convertDurationToPixel(currentPositionMs - mProgramStartTimeMs);
538         mTimeText.setTranslationX(currentTimePositionPixel + mTimeTextLeftMargin);
539         setTextIfNeeded(mTimeText, getTimeString(currentPositionMs));
540         mTimeIndicator.setTranslationX(currentTimePositionPixel + mTimeIndicatorLeftMargin);
541     }
542 
updateProgress()543     private void updateProgress() {
544         if (isEnabled()) {
545             long progressStartTimeMs =
546                     Math.min(
547                             mProgramEndTimeMs,
548                             Math.max(
549                                     mProgramStartTimeMs, mTimeShiftManager.getRecordStartTimeMs()));
550             long currentPlayingTimeMs =
551                     Math.min(
552                             mProgramEndTimeMs,
553                             Math.max(
554                                     mProgramStartTimeMs, mTimeShiftManager.getCurrentPositionMs()));
555             long progressEndTimeMs =
556                     Math.min(
557                             mProgramEndTimeMs,
558                             Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordEndTimeMs()));
559             mProgress.setProgressRange(
560                     progressStartTimeMs - mProgramStartTimeMs,
561                     progressEndTimeMs - mProgramStartTimeMs);
562             mProgress.setProgress(currentPlayingTimeMs - mProgramStartTimeMs);
563         } else {
564             mProgress.setProgressRange(0, 0);
565         }
566     }
567 
updateRecTimeText()568     private void updateRecTimeText() {
569         if (isEnabled()) {
570             mProgramStartTimeText.setVisibility(View.VISIBLE);
571             setTextIfNeeded(mProgramStartTimeText, getTimeString(mProgramStartTimeMs));
572             mProgramEndTimeText.setVisibility(View.VISIBLE);
573             setTextIfNeeded(mProgramEndTimeText, getTimeString(mProgramEndTimeMs));
574         } else {
575             mProgramStartTimeText.setVisibility(View.GONE);
576             mProgramEndTimeText.setVisibility(View.GONE);
577         }
578     }
579 
updateButtons()580     private void updateButtons() {
581         if (isEnabled()) {
582             mPlayPauseButton.setVisibility(View.VISIBLE);
583             mJumpPreviousButton.setVisibility(View.VISIBLE);
584             mJumpNextButton.setVisibility(View.VISIBLE);
585             mRewindButton.setVisibility(View.VISIBLE);
586             mFastForwardButton.setVisibility(View.VISIBLE);
587         } else {
588             mPlayPauseButton.setVisibility(View.GONE);
589             mJumpPreviousButton.setVisibility(View.GONE);
590             mJumpNextButton.setVisibility(View.GONE);
591             mRewindButton.setVisibility(View.GONE);
592             mFastForwardButton.setVisibility(View.GONE);
593             return;
594         }
595 
596         if (mTimeShiftManager.getPlayStatus() == TimeShiftManager.PLAY_STATUS_PAUSED) {
597             mPlayPauseButton.setImageResId(R.drawable.lb_ic_play);
598             mPlayPauseButton.setEnabled(
599                     mTimeShiftManager.isActionEnabled(TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY));
600         } else {
601             mPlayPauseButton.setImageResId(R.drawable.lb_ic_pause);
602             mPlayPauseButton.setEnabled(
603                     mTimeShiftManager.isActionEnabled(TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE));
604         }
605         mJumpPreviousButton.setEnabled(
606                 mTimeShiftManager.isActionEnabled(
607                         TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS));
608         mRewindButton.setEnabled(
609                 mTimeShiftManager.isActionEnabled(TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND));
610         mFastForwardButton.setEnabled(
611                 mTimeShiftManager.isActionEnabled(
612                         TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD));
613         mJumpNextButton.setEnabled(
614                 mTimeShiftManager.isActionEnabled(
615                         TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT));
616         mJumpPreviousButton.setVisibility(VISIBLE);
617         mJumpNextButton.setVisibility(VISIBLE);
618         updateButtonMargin();
619 
620         PlayControlsButton button;
621         if (mTimeShiftManager.getPlayDirection() == TimeShiftManager.PLAY_DIRECTION_FORWARD) {
622             mRewindButton.setLabel(null);
623             button = mFastForwardButton;
624         } else {
625             mFastForwardButton.setLabel(null);
626             button = mRewindButton;
627         }
628         if (mTimeShiftManager.getDisplayedPlaySpeed() == TimeShiftManager.PLAY_SPEED_1X) {
629             button.setLabel(null);
630         } else {
631             button.setLabel(
632                     getResources()
633                             .getString(
634                                     R.string.play_controls_speed,
635                                     mTimeShiftManager.getDisplayedPlaySpeed()));
636         }
637     }
638 
updateRecordButton()639     private void updateRecordButton() {
640         if (isEnabled()) {
641             mRecordButton.setVisibility(VISIBLE);
642         } else {
643             mRecordButton.setVisibility(GONE);
644             return;
645         }
646         if (!(mDvrManager != null
647                 && mDvrManager.isChannelRecordable(mMainActivity.getCurrentChannel()))) {
648             mRecordButton.setVisibility(View.GONE);
649             updateButtonMargin();
650             return;
651         }
652         mRecordButton.setVisibility(View.VISIBLE);
653         updateButtonMargin();
654         if (isCurrentChannelRecording()) {
655             mRecordButton.setImageResId(R.drawable.ic_record_stop);
656         } else {
657             mRecordButton.setImageResId(R.drawable.ic_record_start);
658         }
659     }
660 
updateButtonMargin()661     private void updateButtonMargin() {
662         int numOfVisibleButtons =
663                 (mJumpPreviousButton.getVisibility() == View.VISIBLE ? 1 : 0)
664                         + (mRewindButton.getVisibility() == View.VISIBLE ? 1 : 0)
665                         + (mPlayPauseButton.getVisibility() == View.VISIBLE ? 1 : 0)
666                         + (mFastForwardButton.getVisibility() == View.VISIBLE ? 1 : 0)
667                         + (mJumpNextButton.getVisibility() == View.VISIBLE ? 1 : 0)
668                         + (mRecordButton.getVisibility() == View.VISIBLE ? 1 : 0);
669         boolean useCompactLayout = numOfVisibleButtons > NORMAL_WIDTH_MAX_BUTTON_COUNT;
670         if (mUseCompactLayout == useCompactLayout) {
671             return;
672         }
673         mUseCompactLayout = useCompactLayout;
674         int margin = mUseCompactLayout ? mCompactButtonMargin : mNormalButtonMargin;
675         updateButtonMargin(mJumpPreviousButton, margin);
676         updateButtonMargin(mRewindButton, margin);
677         updateButtonMargin(mPlayPauseButton, margin);
678         updateButtonMargin(mFastForwardButton, margin);
679         updateButtonMargin(mJumpNextButton, margin);
680         updateButtonMargin(mRecordButton, margin);
681     }
682 
updateButtonMargin(PlayControlsButton button, int margin)683     private void updateButtonMargin(PlayControlsButton button, int margin) {
684         MarginLayoutParams params = (MarginLayoutParams) button.getLayoutParams();
685         params.setMargins(margin, 0, margin, 0);
686         button.setLayoutParams(params);
687     }
688 
getTimeString(long timeMs)689     private String getTimeString(long timeMs) {
690         return mTimeFormat.format(timeMs);
691     }
692 
convertDurationToPixel(long duration)693     private int convertDurationToPixel(long duration) {
694         if (mProgramEndTimeMs <= mProgramStartTimeMs) {
695             return 0;
696         }
697         return (int) (duration * mTimelineWidth / (mProgramEndTimeMs - mProgramStartTimeMs));
698     }
699 
700     @Override
onDetachedFromWindow()701     public void onDetachedFromWindow() {
702         super.onDetachedFromWindow();
703         if (mDvrDataManager != null) {
704             mDvrDataManager.removeScheduledRecordingListener(mScheduledRecordingListener);
705         }
706     }
707 
setTextIfNeeded(TextView textView, String text)708     private void setTextIfNeeded(TextView textView, String text) {
709         if (!TextUtils.equals(textView.getText(), text)) {
710             textView.setText(text);
711         }
712     }
713 }
714