blob: 058d5108c3fe2de4d00d482635c105ced17d26a9 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tv.menu;
import android.content.Context;
import android.content.res.Resources;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.android.tv.R;
import com.android.tv.TimeShiftManager;
import com.android.tv.TimeShiftManager.TimeShiftActionId;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Program;
import com.android.tv.menu.Menu.MenuShowReason;
public class PlayControlsRowView extends MenuRowView {
// Dimensions
private final int mTimeIndicatorLeftMargin;
private final int mTimeTextLeftMargin;
private final int mTimelineWidth;
// Views
private View mBackgroundView;
private View mTimeIndicator;
private TextView mTimeText;
private View mProgressEmptyBefore;
private View mProgressWatched;
private View mProgressBuffered;
private View mProgressEmptyAfter;
private View mControlBar;
private PlayControlsButton mJumpPreviousButton;
private PlayControlsButton mRewindButton;
private PlayControlsButton mPlayPauseButton;
private PlayControlsButton mFastForwardButton;
private PlayControlsButton mJumpNextButton;
private TextView mProgramStartTimeText;
private TextView mProgramEndTimeText;
private View mUnavailableMessageText;
private TimeShiftManager mTimeShiftManager;
private final java.text.DateFormat mTimeFormat;
private long mProgramStartTimeMs;
private long mProgramEndTimeMs;
public PlayControlsRowView(Context context) {
this(context, null);
}
public PlayControlsRowView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PlayControlsRowView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public PlayControlsRowView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
Resources res = context.getResources();
mTimeIndicatorLeftMargin =
- res.getDimensionPixelSize(R.dimen.play_controls_time_indicator_width) / 2;
mTimeTextLeftMargin =
- res.getDimensionPixelOffset(R.dimen.play_controls_time_width) / 2;
mTimelineWidth = res.getDimensionPixelSize(R.dimen.play_controls_width);
mTimeFormat = DateFormat.getTimeFormat(context);
}
@Override
protected int getContentsViewId() {
return R.id.play_controls;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// Clip the ViewGroup(body) to the rounded rectangle of outline.
findViewById(R.id.body).setClipToOutline(true);
mBackgroundView = findViewById(R.id.background);
mTimeIndicator = findViewById(R.id.time_indicator);
mTimeText = (TextView) findViewById(R.id.time_text);
mProgressEmptyBefore = findViewById(R.id.timeline_bg_start);
mProgressWatched = findViewById(R.id.watched);
mProgressBuffered = findViewById(R.id.buffered);
mProgressEmptyAfter = findViewById(R.id.timeline_bg_end);
mControlBar = findViewById(R.id.play_control_bar);
mJumpPreviousButton = (PlayControlsButton) findViewById(R.id.jump_previous);
mRewindButton = (PlayControlsButton) findViewById(R.id.rewind);
mPlayPauseButton = (PlayControlsButton) findViewById(R.id.play_pause);
mFastForwardButton = (PlayControlsButton) findViewById(R.id.fast_forward);
mJumpNextButton = (PlayControlsButton) findViewById(R.id.jump_next);
mProgramStartTimeText = (TextView) findViewById(R.id.program_start_time);
mProgramEndTimeText = (TextView) findViewById(R.id.program_end_time);
mUnavailableMessageText = findViewById(R.id.unavailable_text);
initializeButton(mJumpPreviousButton, R.drawable.lb_ic_skip_previous,
R.string.play_controls_description_skip_previous, new Runnable() {
@Override
public void run() {
if (mTimeShiftManager.isAvailable()) {
mTimeShiftManager.jumpToPrevious();
updateAll();
}
}
});
initializeButton(mRewindButton, R.drawable.lb_ic_fast_rewind,
R.string.play_controls_description_fast_rewind, new Runnable() {
@Override
public void run() {
if (mTimeShiftManager.isAvailable()) {
mTimeShiftManager.rewind();
updateButtons();
}
}
});
initializeButton(mPlayPauseButton, R.drawable.lb_ic_play,
R.string.play_controls_description_play_pause, new Runnable() {
@Override
public void run() {
if (mTimeShiftManager.isAvailable()) {
mTimeShiftManager.togglePlayPause();
updateButtons();
}
}
});
initializeButton(mFastForwardButton, R.drawable.lb_ic_fast_forward,
R.string.play_controls_description_fast_forward, new Runnable() {
@Override
public void run() {
if (mTimeShiftManager.isAvailable()) {
mTimeShiftManager.fastForward();
updateButtons();
}
}
});
initializeButton(mJumpNextButton, R.drawable.lb_ic_skip_next,
R.string.play_controls_description_skip_next, new Runnable() {
@Override
public void run() {
if (mTimeShiftManager.isAvailable()) {
mTimeShiftManager.jumpToNext();
updateAll();
}
}
});
}
private void initializeButton(PlayControlsButton button, int imageResId,
int descriptionId, Runnable clickAction) {
button.setImageResId(imageResId);
button.setAction(clickAction);
button.findViewById(R.id.button)
.setContentDescription(getResources().getString(descriptionId));
}
@Override
public void onBind(MenuRow row) {
super.onBind(row);
PlayControlsRow playControlsRow = (PlayControlsRow) row;
mTimeShiftManager = playControlsRow.getTimeShiftManager();
mTimeShiftManager.setListener(new TimeShiftManager.Listener() {
@Override
public void onAvailabilityChanged() {
updateMenuVisibility();
PlayControlsRowView.this.onAvailabilityChanged();
}
@Override
public void onPlayStatusChanged(int status) {
updateMenuVisibility();
if (mTimeShiftManager.isAvailable()) {
updateAll();
}
}
@Override
public void onRecordTimeRangeChanged() {
if (!mTimeShiftManager.isAvailable()) {
return;
}
updateAll();
}
@Override
public void onCurrentPositionChanged() {
if (!mTimeShiftManager.isAvailable()) {
return;
}
initializeTimeline();
updateAll();
}
@Override
public void onProgramInfoChanged() {
if (!mTimeShiftManager.isAvailable()) {
return;
}
initializeTimeline();
updateAll();
}
@Override
public void onActionEnabledChanged(@TimeShiftActionId int actionId, boolean enabled) {
// Move focus to the play/pause button when the PREVIOUS, NEXT, REWIND or
// FAST_FORWARD button is clicked and the button becomes disabled.
// No need to update the UI here because the UI will be updated by other callbacks.
if (!enabled &&
((actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS
&& mJumpPreviousButton.hasFocus())
|| (actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND
&& mRewindButton.hasFocus())
|| (actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD
&& mFastForwardButton.hasFocus())
|| (actionId == TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT
&& mJumpNextButton.hasFocus()))) {
mPlayPauseButton.requestFocus();
}
}
});
onAvailabilityChanged();
}
private void onAvailabilityChanged() {
if (mTimeShiftManager.isAvailable()) {
setEnabled(true);
initializeTimeline();
mBackgroundView.setEnabled(true);
} else {
setEnabled(false);
mBackgroundView.setEnabled(false);
}
updateAll();
}
private void initializeTimeline() {
if (mTimeShiftManager.isRecordingPlayback()) {
mProgramStartTimeMs = mTimeShiftManager.getRecordStartTimeMs();
mProgramEndTimeMs = mTimeShiftManager.getRecordEndTimeMs();
} else {
Program program = mTimeShiftManager.getProgramAt(
mTimeShiftManager.getCurrentPositionMs());
mProgramStartTimeMs = program.getStartTimeUtcMillis();
mProgramEndTimeMs = program.getEndTimeUtcMillis();
}
SoftPreconditions.checkArgument(mProgramStartTimeMs <= mProgramEndTimeMs);
}
private void updateMenuVisibility() {
boolean keepMenuVisible =
mTimeShiftManager.isAvailable() && !mTimeShiftManager.isNormalPlaying();
getMenu().setKeepVisible(keepMenuVisible);
}
@Override
public void onSelected(boolean showTitle) {
super.onSelected(showTitle);
updateAll();
postHideRippleAnimation();
}
@Override
public void initialize(@MenuShowReason int reason) {
super.initialize(reason);
switch (reason) {
case Menu.REASON_PLAY_CONTROLS_JUMP_TO_PREVIOUS:
if (mTimeShiftManager.isActionEnabled(
TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS)) {
setInitialFocusView(mJumpPreviousButton);
} else {
setInitialFocusView(mPlayPauseButton);
}
break;
case Menu.REASON_PLAY_CONTROLS_REWIND:
if (mTimeShiftManager.isActionEnabled(
TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND)) {
setInitialFocusView(mRewindButton);
} else {
setInitialFocusView(mPlayPauseButton);
}
break;
case Menu.REASON_PLAY_CONTROLS_FAST_FORWARD:
if (mTimeShiftManager.isActionEnabled(
TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD)) {
setInitialFocusView(mFastForwardButton);
} else {
setInitialFocusView(mPlayPauseButton);
}
break;
case Menu.REASON_PLAY_CONTROLS_JUMP_TO_NEXT:
if (mTimeShiftManager.isActionEnabled(
TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT)) {
setInitialFocusView(mJumpNextButton);
} else {
setInitialFocusView(mPlayPauseButton);
}
break;
case Menu.REASON_PLAY_CONTROLS_PLAY_PAUSE:
case Menu.REASON_PLAY_CONTROLS_PLAY:
case Menu.REASON_PLAY_CONTROLS_PAUSE:
default:
setInitialFocusView(mPlayPauseButton);
break;
}
postHideRippleAnimation();
}
private void postHideRippleAnimation() {
// Focus may be changed in another message if requestFocus is called in this message.
// After the focus is actually changed, hideRippleAnimation should run
// to reflect the result of the focus change. To be sure, hideRippleAnimation is posted.
post(new Runnable() {
@Override
public void run() {
mJumpPreviousButton.hideRippleAnimation();
mRewindButton.hideRippleAnimation();
mPlayPauseButton.hideRippleAnimation();
mFastForwardButton.hideRippleAnimation();
mJumpNextButton.hideRippleAnimation();
}
});
}
@Override
protected void onChildFocusChange(View v, boolean hasFocus) {
super.onChildFocusChange(v, hasFocus);
if ((v.getParent().equals(mRewindButton) || v.getParent().equals(mFastForwardButton))
&& !hasFocus) {
if (mTimeShiftManager.getPlayStatus() == TimeShiftManager.PLAY_STATUS_PLAYING) {
mTimeShiftManager.play();
updateButtons();
}
}
}
private void updateAll() {
updateTime();
updateProgress();
updateRecTimeText();
updateButtons();
}
private void updateTime() {
if (isEnabled()) {
mTimeText.setVisibility(View.VISIBLE);
mTimeIndicator.setVisibility(View.VISIBLE);
} else {
mTimeText.setVisibility(View.INVISIBLE);
mTimeIndicator.setVisibility(View.INVISIBLE);
return;
}
long currentPositionMs = mTimeShiftManager.getCurrentPositionMs();
ViewGroup.MarginLayoutParams params =
(ViewGroup.MarginLayoutParams) mTimeText.getLayoutParams();
int currentTimePositionPixel =
convertDurationToPixel(currentPositionMs - mProgramStartTimeMs);
params.leftMargin = currentTimePositionPixel + mTimeTextLeftMargin;
mTimeText.setLayoutParams(params);
mTimeText.setText(getTimeString(currentPositionMs));
params = (ViewGroup.MarginLayoutParams) mTimeIndicator.getLayoutParams();
params.leftMargin = currentTimePositionPixel + mTimeIndicatorLeftMargin;
mTimeIndicator.setLayoutParams(params);
}
private void updateProgress() {
if (isEnabled()) {
mProgressWatched.setVisibility(View.VISIBLE);
mProgressBuffered.setVisibility(View.VISIBLE);
mProgressEmptyAfter.setVisibility(View.VISIBLE);
} else {
mProgressWatched.setVisibility(View.INVISIBLE);
mProgressBuffered.setVisibility(View.INVISIBLE);
mProgressEmptyAfter.setVisibility(View.INVISIBLE);
if (mProgramStartTimeMs < mProgramEndTimeMs) {
layoutProgress(mProgressEmptyBefore, mProgramStartTimeMs, mProgramEndTimeMs);
} else {
// Not initialized yet.
layoutProgress(mProgressEmptyBefore, mTimelineWidth);
}
return;
}
long progressStartTimeMs = Math.min(mProgramEndTimeMs,
Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordStartTimeMs()));
long currentPlayingTimeMs = Math.min(mProgramEndTimeMs,
Math.max(mProgramStartTimeMs, mTimeShiftManager.getCurrentPositionMs()));
long progressEndTimeMs = Math.min(mProgramEndTimeMs,
Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordEndTimeMs()));
layoutProgress(mProgressEmptyBefore, mProgramStartTimeMs, progressStartTimeMs);
layoutProgress(mProgressWatched, progressStartTimeMs, currentPlayingTimeMs);
layoutProgress(mProgressBuffered, currentPlayingTimeMs, progressEndTimeMs);
}
private void layoutProgress(View progress, long progressStartTimeMs, long progressEndTimeMs) {
layoutProgress(progress, Math.max(0,
convertDurationToPixel(progressEndTimeMs - progressStartTimeMs)) + 1);
}
private void layoutProgress(View progress, int width) {
ViewGroup.MarginLayoutParams params =
(ViewGroup.MarginLayoutParams) progress.getLayoutParams();
params.width = width;
progress.setLayoutParams(params);
}
private void updateRecTimeText() {
if (isEnabled()) {
if (mTimeShiftManager.isRecordingPlayback()) {
mProgramStartTimeText.setVisibility(View.GONE);
} else {
mProgramStartTimeText.setVisibility(View.VISIBLE);
mProgramStartTimeText.setText(getTimeString(mProgramStartTimeMs));
}
mProgramEndTimeText.setVisibility(View.VISIBLE);
mProgramEndTimeText.setText(getTimeString(mProgramEndTimeMs));
} else {
mProgramStartTimeText.setVisibility(View.GONE);
mProgramEndTimeText.setVisibility(View.GONE);
}
}
private void updateButtons() {
if (isEnabled()) {
mControlBar.setVisibility(View.VISIBLE);
mUnavailableMessageText.setVisibility(View.GONE);
} else {
mControlBar.setVisibility(View.INVISIBLE);
mUnavailableMessageText.setVisibility(View.VISIBLE);
return;
}
if (mTimeShiftManager.getPlayStatus() == TimeShiftManager.PLAY_STATUS_PAUSED) {
mPlayPauseButton.setImageResId(R.drawable.lb_ic_play);
mPlayPauseButton.setEnabled(mTimeShiftManager.isActionEnabled(
TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY));
} else {
mPlayPauseButton.setImageResId(R.drawable.lb_ic_pause);
mPlayPauseButton.setEnabled(mTimeShiftManager.isActionEnabled(
TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE));
}
mJumpPreviousButton.setEnabled(mTimeShiftManager.isActionEnabled(
TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS));
mRewindButton.setEnabled(mTimeShiftManager.isActionEnabled(
TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND));
mFastForwardButton.setEnabled(mTimeShiftManager.isActionEnabled(
TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD));
mJumpNextButton.setEnabled(mTimeShiftManager.isActionEnabled(
TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT));
PlayControlsButton button;
if (mTimeShiftManager.getPlayDirection() == TimeShiftManager.PLAY_DIRECTION_FORWARD) {
mRewindButton.setLabel(null);
button = mFastForwardButton;
} else {
mFastForwardButton.setLabel(null);
button = mRewindButton;
}
if (mTimeShiftManager.getDisplayedPlaySpeed() == TimeShiftManager.PLAY_SPEED_1X) {
button.setLabel(null);
} else {
button.setLabel(getResources().getString(R.string.play_controls_speed,
mTimeShiftManager.getDisplayedPlaySpeed()));
}
}
private String getTimeString(long timeMs) {
return mTimeShiftManager.isRecordingPlayback()
? DateUtils.formatElapsedTime(timeMs / 1000)
: mTimeFormat.format(timeMs);
}
private int convertDurationToPixel(long duration) {
if (mProgramEndTimeMs <= mProgramStartTimeMs) {
return 0;
}
return (int) (duration * mTimelineWidth / (mProgramEndTimeMs - mProgramStartTimeMs));
}
}