blob: 6eea6ae7bc42802e19a551b85944dc73865df2d4 [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.common.recording;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.PlaybackParams;
import android.media.tv.TvContentRating;
import android.media.tv.TvInputService;
import android.media.tv.TvTrackInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.Surface;
import com.android.tv.common.feature.CommonFeatures;
import java.util.List;
/**
* {@link TvInputService} class that supports recording and playback.
*/
@TargetApi(Build.VERSION_CODES.M) // TODO(DVR): set to N
public abstract class RecordingTvInputService extends TvInputService {
private static final String TAG = "DvrTvInputService";
private static final boolean DEBUG = true;
@Override
public final Session onCreateSession(String inputId) {
if (CommonFeatures.DVR.isEnabled(this)) {
return new InternalSession(this, inputId);
} else {
return onCreatePlaybackSession(inputId);
}
}
/**
* Called when {@link com.android.tv.common.recording.DvrSession#connect} is called.
*/
protected TvRecording.RecordingSession onCreateDvrSession(String inputId) {
return null;
}
protected abstract PlaybackSession onCreatePlaybackSession(String inputId);
private class InternalSession extends TvInputService.Session {
final String mInputId;
BaseSession mSessionImpl;
public InternalSession(Context context, String inputId) {
super(context);
mInputId = inputId;
}
@Override
public void onRelease() {
if (mSessionImpl != null) {
mSessionImpl.onRelease();
}
}
@Override
public boolean onSetSurface(Surface surface) {
return mSessionImpl.onSetSurface(surface);
}
@Override
public void onSetStreamVolume(float volume) {
mSessionImpl.onSetStreamVolume(volume);
}
@Override
public boolean onTune(Uri channelUri) {
return mSessionImpl.onTune(channelUri);
}
@Override
public void onAppPrivateCommand(String action, Bundle data) {
if (action.equals(RecordingUtils.APP_PRIV_CREATE_DVR_SESSION)) {
if (mSessionImpl == null) {
mSessionImpl = onCreateDvrSession(mInputId);
if (mSessionImpl != null) {
mSessionImpl.setPassthroughSession(this);
notifySessionEvent(RecordingUtils.EVENT_TYPE_CONNECTED, null);
}
}
} else if (action.equals(RecordingUtils.APP_PRIV_CREATE_PLAYBACK_SESSION)) {
if (mSessionImpl == null) {
mSessionImpl = onCreatePlaybackSession(mInputId);
if (mSessionImpl != null) {
mSessionImpl.setPassthroughSession(this);
}
}
} else {
if (mSessionImpl == null) {
throw new IllegalStateException();
}
mSessionImpl.onAppPrivateCommand(action, data);
}
}
@Override
public android.view.View onCreateOverlayView() {
return mSessionImpl.onCreateOverlayView();
}
@Override
public boolean onGenericMotionEvent(android.view.MotionEvent event) {
return mSessionImpl.onGenericMotionEvent(event);
}
@Override
public boolean onKeyDown(int keyCode, android.view.KeyEvent event) {
return mSessionImpl.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyLongPress(int keyCode, android.view.KeyEvent event) {
return mSessionImpl.onKeyLongPress(keyCode, event);
}
@Override
public boolean onKeyMultiple(int keyCode, int count, android.view.KeyEvent event) {
return mSessionImpl.onKeyMultiple(keyCode, count, event);
}
@Override
public boolean onKeyUp(int keyCode, android.view.KeyEvent event) {
return mSessionImpl.onKeyUp(keyCode, event);
}
@Override
public void onOverlayViewSizeChanged(int width, int height) {
mSessionImpl.onOverlayViewSizeChanged(width, height);
}
@Override
public boolean onSelectTrack(int type, String trackId) {
return mSessionImpl.onSelectTrack(type, trackId);
}
@Override
public void onSetMain(boolean isMain) {
mSessionImpl.onSetMain(isMain);
}
@Override
public void onSurfaceChanged(int format, int width, int height) {
mSessionImpl.onSurfaceChanged(format, width, height);
}
@Override
public long onTimeShiftGetCurrentPosition() {
return mSessionImpl.onTimeShiftGetCurrentPosition();
}
@Override
public long onTimeShiftGetStartPosition() {
return mSessionImpl.onTimeShiftGetStartPosition();
}
@Override
public void onTimeShiftPause() {
mSessionImpl.onTimeShiftPause();
}
@Override
public void onTimeShiftResume() {
mSessionImpl.onTimeShiftResume();
}
@Override
public void onTimeShiftSeekTo(long timeMs) {
mSessionImpl.onTimeShiftSeekTo(timeMs);
}
@Override
public void onTimeShiftSetPlaybackParams(PlaybackParams params) {
mSessionImpl.onTimeShiftSetPlaybackParams(params);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mSessionImpl.onTouchEvent(event);
}
@Override
public boolean onTrackballEvent(MotionEvent event) {
return mSessionImpl.onTrackballEvent(event);
}
@Override
public boolean onTune(Uri channelUri, Bundle params) {
return mSessionImpl.onTune(channelUri, params);
}
@Override
public void onUnblockContent(TvContentRating unblockedRating) {
mSessionImpl.onUnblockContent(unblockedRating);
}
@Override
public void onSetCaptionEnabled(boolean enabled) {
mSessionImpl.onSetCaptionEnabled(enabled);
}
}
/**
* Base class for {@link PlaybackSession} and {@link TvRecording.RecordingSession}. Do not use it directly
* outside of this class.
*/
public static abstract class BaseSession extends TvInputService.Session {
private Session mPassthroughSession;
public BaseSession(Context context) {
super(context);
}
private void setPassthroughSession(Session passthroughSession) {
mPassthroughSession = passthroughSession;
}
@Override
public void setOverlayViewEnabled(boolean enable) {
if (mPassthroughSession != null) {
mPassthroughSession.setOverlayViewEnabled(enable);
} else {
super.setOverlayViewEnabled(enable);
}
}
@Override
public void notifyChannelRetuned(Uri channelUri) {
if (mPassthroughSession != null) {
mPassthroughSession.notifyChannelRetuned(channelUri);
} else {
super.notifyChannelRetuned(channelUri);
}
}
@Override
public void notifyContentAllowed() {
if (mPassthroughSession != null) {
mPassthroughSession.notifyContentAllowed();
} else {
super.notifyContentAllowed();
}
}
@Override
public void notifyContentBlocked(TvContentRating rating) {
if (mPassthroughSession != null) {
mPassthroughSession.notifyContentBlocked(rating);
} else {
super.notifyContentBlocked(rating);
}
}
@Override
public void notifySessionEvent(String eventType, Bundle eventArgs) {
if (mPassthroughSession != null) {
mPassthroughSession.notifySessionEvent(eventType, eventArgs);
} else {
super.notifySessionEvent(eventType, eventArgs);
}
}
@Override
public void notifyTimeShiftStatusChanged(int status) {
if (mPassthroughSession != null) {
mPassthroughSession.notifyTimeShiftStatusChanged(status);
} else {
super.notifyTimeShiftStatusChanged(status);
}
}
@Override
public void notifyTracksChanged(List<TvTrackInfo> tracks) {
if (mPassthroughSession != null) {
mPassthroughSession.notifyTracksChanged(tracks);
} else {
super.notifyTracksChanged(tracks);
}
}
@Override
public void notifyTrackSelected(int type, String trackId) {
if (mPassthroughSession != null) {
mPassthroughSession.notifyTrackSelected(type, trackId);
} else {
super.notifyTrackSelected(type, trackId);
}
}
@Override
public void notifyVideoAvailable() {
if (mPassthroughSession != null) {
mPassthroughSession.notifyVideoAvailable();
} else {
super.notifyVideoAvailable();
}
}
@Override
public void notifyVideoUnavailable(int reason) {
if (mPassthroughSession != null) {
mPassthroughSession.notifyVideoUnavailable(reason);
} else {
super.notifyVideoUnavailable(reason);
}
}
}
/**
* Session linked to {@link android.media.tv.TvView} to tune to a channel or play an recording.
*/
public static abstract class PlaybackSession extends BaseSession {
private boolean mIsRecordingPlayback;
public PlaybackSession(Context context) {
super(context);
}
/**
* Returns {@code true}, if the current playback is for a recording.
*/
public boolean isRecordingPlayback() {
return mIsRecordingPlayback;
}
/**
* Called when it is requested to play an recording {@code mediaUri}. When playback and
* rendering starts, {@link #notifyVideoAvailable} should be called.
*/
public void onPlayMedia(Uri mediaUri) { }
/**
* Notifies TimeShift end position. It should have the form like onTimeShiftEndPosition.
* But, it's not trivial to add that in the prototyping. The method is recommended to be
* called inside {@link #onTimeShiftGetStartPosition()}, when a recording is played.
*/
public void notifyTimeShiftEndPosition(long endPosition) {
Bundle params = new Bundle();
params.putLong(RecordingUtils.BUNDLE_TIMESHIFT_END_POSITION, endPosition);
notifySessionEvent(RecordingUtils.EVENT_TYPE_TIMESHIFT_END_POSITION, params);
}
@Override
public final boolean onTune(Uri channelUri, Bundle params) {
if (params != null && params.getBoolean(RecordingUtils.BUNDLE_IS_DVR, false)) {
notifySessionEvent(RecordingUtils.EVENT_TYPE_CONNECTED, null);
return true;
} else if (params != null && params.containsKey(RecordingUtils.BUNDLE_MEDIA_URI)) {
mIsRecordingPlayback = true;
onPlayMedia(Uri.parse(params.getString(RecordingUtils.BUNDLE_MEDIA_URI)));
return true;
} else {
mIsRecordingPlayback = false;
return onTune(channelUri);
}
}
}
}