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