diff options
Diffstat (limited to 'tests/input/src/com/android/tv/testinput/TestTvInputService.java')
-rw-r--r-- | tests/input/src/com/android/tv/testinput/TestTvInputService.java | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/tests/input/src/com/android/tv/testinput/TestTvInputService.java b/tests/input/src/com/android/tv/testinput/TestTvInputService.java new file mode 100644 index 00000000..98ac9438 --- /dev/null +++ b/tests/input/src/com/android/tv/testinput/TestTvInputService.java @@ -0,0 +1,345 @@ +/* + * 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.testinput; + +import android.content.ComponentName; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.media.PlaybackParams; +import android.media.tv.TvContract; +import android.media.tv.TvInputManager; +import android.media.tv.TvInputService; +import android.media.tv.TvTrackInfo; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Surface; + +import com.android.tv.common.TvCommonConstants; +import com.android.tv.testing.ChannelInfo; +import com.android.tv.testing.testinput.ChannelState; + +import java.util.Date; + +/** + * Simple TV input service which provides test channels. + */ +public class TestTvInputService extends TvInputService { + private static final String TAG = "TestTvInputServices"; + private static final int REFRESH_DELAY_MS = 1000 / 5; + private static final boolean DEBUG = false; + private TestInputControl mBackend = TestInputControl.getInstance(); + + public static String buildInputId(Context context) { + return TvContract.buildInputId(new ComponentName(context, TestTvInputService.class)); + } + + @Override + public void onCreate() { + super.onCreate(); + mBackend.init(this, buildInputId(this)); + } + + @Override + public Session onCreateSession(String inputId) { + Log.v(TAG, "Creating session for " + inputId); + return new SimpleSessionImpl(this); + } + + /** + * Simple session implementation that just display some text. + */ + private class SimpleSessionImpl extends Session { + private static final int MSG_SEEK = 1000; + private static final int SEEK_DELAY_MS = 300; + + private final Paint mTextPaint = new Paint(); + private final DrawRunnable mDrawRunnable = new DrawRunnable(); + private Surface mSurface = null; + private ChannelInfo mChannel = null; + private ChannelState mCurrentState = null; + private String mCurrentVideoTrackId = null; + private String mCurrentAudioTrackId = null; + + private long mRecordStartTimeMs; + private long mPausedTimeMs; + // The time in milliseconds when the current position is lastly updated. + private long mLastCurrentPositionUpdateTimeMs; + // The current playback position. + private long mCurrentPositionMs; + // The current playback speed rate. + private float mSpeed; + + private final Handler mHandler = new Handler(Looper.myLooper()) { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_SEEK) { + // Actually, this input doesn't play any videos, it just shows the image. + // So we should simulate the playback here by changing the current playback + // position periodically in order to test the time shift. + // If the playback is paused, the current playback position doesn't need to be + // changed. + if (mPausedTimeMs == 0) { + long currentTimeMs = System.currentTimeMillis(); + mCurrentPositionMs += (long) ((currentTimeMs + - mLastCurrentPositionUpdateTimeMs) * mSpeed); + mCurrentPositionMs = Math.max(mRecordStartTimeMs, + Math.min(mCurrentPositionMs, currentTimeMs)); + mLastCurrentPositionUpdateTimeMs = currentTimeMs; + } + sendEmptyMessageDelayed(MSG_SEEK, SEEK_DELAY_MS); + } + super.handleMessage(msg); + } + }; + + SimpleSessionImpl(Context context) { + super(context); + mTextPaint.setColor(Color.BLACK); + mTextPaint.setTextSize(150); + mHandler.post(mDrawRunnable); + if (DEBUG) { + Log.v(TAG, "Created session " + this); + } + } + + private void setAudioTrack(String selectedAudioTrackId) { + Log.i(TAG, "Set audio track to " + selectedAudioTrackId); + mCurrentAudioTrackId = selectedAudioTrackId; + notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, mCurrentAudioTrackId); + } + + private void setVideoTrack(String selectedVideoTrackId) { + Log.i(TAG, "Set video track to " + selectedVideoTrackId); + mCurrentVideoTrackId = selectedVideoTrackId; + notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, mCurrentVideoTrackId); + } + + @Override + public void onRelease() { + if (DEBUG) { + Log.v(TAG, "Releasing session " + this); + } + mDrawRunnable.cancel(); + mHandler.removeCallbacks(mDrawRunnable); + mSurface = null; + mChannel = null; + mCurrentState = null; + } + + @Override + public boolean onSetSurface(Surface surface) { + synchronized (mDrawRunnable) { + mSurface = surface; + } + if (surface != null) { + if (DEBUG) { + Log.v(TAG, "Surface set"); + } + } else { + if (DEBUG) { + Log.v(TAG, "Surface unset"); + } + } + + return true; + } + + @Override + public void onSurfaceChanged(int format, int width, int height) { + super.onSurfaceChanged(format, width, height); + Log.d(TAG, "format=" + format + " width=" + width + " height=" + height); + } + + @Override + public void onSetStreamVolume(float volume) { + // No-op + } + + @Override + public boolean onTune(Uri channelUri) { + Log.i(TAG, "Tune to " + channelUri); + ChannelInfo info = mBackend.getChannelInfo(channelUri); + synchronized (mDrawRunnable) { + if (info == null || mChannel == null + || mChannel.originalNetworkId != info.originalNetworkId) { + mCurrentState = null; + } + mChannel = info; + mCurrentVideoTrackId = null; + mCurrentAudioTrackId = null; + } + if (mChannel == null) { + Log.i(TAG, "Channel not found for " + channelUri); + notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); + } else { + Log.i(TAG, "Tuning to " + mChannel); + } + if (TvCommonConstants.HAS_TIME_SHIFT_API) { + notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_AVAILABLE); + mRecordStartTimeMs = mCurrentPositionMs = mLastCurrentPositionUpdateTimeMs + = System.currentTimeMillis(); + mPausedTimeMs = 0; + mHandler.sendEmptyMessageDelayed(MSG_SEEK, SEEK_DELAY_MS); + mSpeed = 1; + } + return true; + } + + @Override + public void onSetCaptionEnabled(boolean enabled) { + // No-op + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + Log.d(TAG, "onKeyDown (keyCode=" + keyCode + ", event=" + event + ")"); + return true; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + Log.d(TAG, "onKeyUp (keyCode=" + keyCode + ", event=" + event + ")"); + return true; + } + + @Override + public long onTimeShiftGetCurrentPosition() { + Log.d(TAG, "currentPositionMs=" + mCurrentPositionMs); + return mCurrentPositionMs; + } + + @Override + public long onTimeShiftGetStartPosition() { + return mRecordStartTimeMs; + } + + @Override + public void onTimeShiftPause() { + mCurrentPositionMs = mPausedTimeMs = mLastCurrentPositionUpdateTimeMs + = System.currentTimeMillis(); + } + + @Override + public void onTimeShiftResume() { + mSpeed = 1; + mPausedTimeMs = 0; + mLastCurrentPositionUpdateTimeMs = System.currentTimeMillis(); + } + + @Override + public void onTimeShiftSeekTo(long timeMs) { + mLastCurrentPositionUpdateTimeMs = System.currentTimeMillis(); + mCurrentPositionMs = Math.max(mRecordStartTimeMs, + Math.min(timeMs, mLastCurrentPositionUpdateTimeMs)); + } + + @Override + public void onTimeShiftSetPlaybackParams(PlaybackParams params) { + mSpeed = params.getSpeed(); + } + + private final class DrawRunnable implements Runnable { + private volatile boolean mIsCanceled = false; + + @Override + public void run() { + if (mIsCanceled) { + return; + } + if (DEBUG) { + Log.v(TAG, "Draw task running"); + } + boolean updatedState = false; + ChannelState oldState; + ChannelState newState = null; + Surface currentSurface; + ChannelInfo currentChannel; + + synchronized (this) { + oldState = mCurrentState; + currentSurface = mSurface; + currentChannel = mChannel; + if (currentChannel != null) { + newState = mBackend.getChannelState(currentChannel.originalNetworkId); + if (oldState == null || newState.getVersion() > oldState.getVersion()) { + mCurrentState = newState; + updatedState = true; + } + } else { + mCurrentState = null; + } + } + + draw(currentSurface, currentChannel); + if (updatedState) { + update(oldState, newState, currentChannel); + } + + if (!mIsCanceled) { + mHandler.postDelayed(this, REFRESH_DELAY_MS); + } + } + + private void update(ChannelState oldState, ChannelState newState, + ChannelInfo currentChannel) { + Log.i(TAG, "Updating channel " + currentChannel.number + " state to " + newState); + notifyTracksChanged(newState.getTrackInfoList()); + if (oldState == null || oldState.getTuneStatus() != newState.getTuneStatus()) { + if (newState.getTuneStatus() == ChannelState.TUNE_STATUS_VIDEO_AVAILABLE) { + notifyVideoAvailable(); + //TODO handle parental controls. + notifyContentAllowed(); + setAudioTrack(newState.getSelectedAudioTrackId()); + setVideoTrack(newState.getSelectedVideoTrackId()); + } else { + notifyVideoUnavailable(newState.getTuneStatus()); + } + } + } + + private void draw(Surface surface, ChannelInfo currentChannel) { + if (surface != null) { + String now = TvCommonConstants.HAS_TIME_SHIFT_API + ? new Date(mCurrentPositionMs).toString() : new Date().toString(); + String name = currentChannel == null ? "Null" : currentChannel.name; + Canvas c = surface.lockCanvas(null); + c.drawColor(0xFF888888); + c.drawText(name, 100f, 200f, mTextPaint); + c.drawText(now, 100f, 400f, mTextPaint); + surface.unlockCanvasAndPost(c); + if (DEBUG) { + Log.v(TAG, "Post to canvas"); + } + } else { + if (DEBUG) { + Log.v(TAG, "No surface"); + } + } + } + + public void cancel() { + mIsCanceled = true; + } + } + } +} |