diff options
Diffstat (limited to 'tuner/tests/unittests/javatests/com/android')
8 files changed, 1141 insertions, 0 deletions
diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/AndroidManifest.xml b/tuner/tests/unittests/javatests/com/android/tv/tuner/AndroidManifest.xml new file mode 100644 index 00000000..9c815600 --- /dev/null +++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.tv.tuner.tests" > + + <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="26" /> + + <instrumentation + android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.tv" /> + + <application android:label="TunerTvInputTests" > + <uses-library android:name="android.test.runner" /> + </application> + +</manifest> diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/FakeTunerHal.java b/tuner/tests/unittests/javatests/com/android/tv/tuner/FakeTunerHal.java new file mode 100644 index 00000000..cc4f6fde --- /dev/null +++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/FakeTunerHal.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 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.tuner; + +public class FakeTunerHal extends TunerHal { + + private boolean mDeviceOpened; + + public FakeTunerHal() { + super(null); + } + + @Override + protected boolean openFirstAvailable() { + mDeviceOpened = true; + getDeliverySystemTypeFromDevice(); + return true; + } + + @Override + protected boolean isDeviceOpen() { + return mDeviceOpened; + } + + @Override + protected long getDeviceId() { + return 0; + } + + @Override + public void close() throws Exception { + mDeviceOpened = false; + } + + @Override + protected void nativeFinalize(long deviceId) {} + + @Override + protected boolean nativeTune( + long deviceId, int frequency, @ModulationType String modulation, int timeoutMs) { + return true; + } + + @Override + protected void nativeAddPidFilter(long deviceId, int pid, @FilterType int filterType) {} + + @Override + protected void nativeCloseAllPidFilters(long deviceId) {} + + @Override + protected void nativeStopTune(long deviceId) {} + + @Override + protected int nativeWriteInBuffer(long deviceId, byte[] javaBuffer, int javaBufferSize) { + return 0; + } + + @Override + protected void nativeSetHasPendingTune(long deviceId, boolean hasPendingTune) {} + + @Override + protected int nativeGetDeliverySystemType(long deviceId) { + return DELIVERY_SYSTEM_ATSC; + } +} diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/FileTunerHal.java b/tuner/tests/unittests/javatests/com/android/tv/tuner/FileTunerHal.java new file mode 100644 index 00000000..73d234e0 --- /dev/null +++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/FileTunerHal.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2016 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.tuner; + +import android.content.Context; +import android.os.SystemClock; +import android.util.Log; +import android.util.SparseBooleanArray; +import com.android.tv.testing.utils.Utils; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Random; + +/** This class simulate the actions happened in TunerHal. */ +public class FileTunerHal extends TunerHal { + + private static final String TAG = "FileTunerHal"; + private static final int DEVICE_ID = 0; + private static final int TS_PACKET_SIZE = 188; + // To keep consistent with tunertvinput_jni, which fit Ethernet MTU (1500) + private static final int TS_PACKET_COUNT_PER_PAYLOAD = 7; + private static final int TS_PAYLOAD_SIZE = TS_PACKET_SIZE * TS_PACKET_COUNT_PER_PAYLOAD; + private static final int TS_SYNC_BYTE = 0x47; + // Make the read size from file and size in nativeWriteInBuffer same to make read logic simpler. + private static final int MIN_READ_UNIT = TS_PACKET_SIZE * TS_PACKET_COUNT_PER_PAYLOAD; + private static final long DELAY_IOCTL_SET_FRONTEND = 100; + private static final long DELAY_IOCTL_WAIT_FRONTEND_LOCKED = 500; + + // The terrestrial broadcast mode (known as 8-VSB) delivers an MPEG-2 TS at rate + // approximately 19.39 Mbps in a 6 MHz channel. (See section #5 of ATSC A/53 Part 2:2011) + private static final double TS_READ_BYTES_PER_MS = 19.39 * 1024 * 1024 / (8 * 1000.0); + private static final long INITIAL_BUFFERED_TS_BYTES = (long) (TS_READ_BYTES_PER_MS * 150); + + private static boolean sIsDeviceOpen; + + private final SparseBooleanArray mPids = new SparseBooleanArray(); + private final byte[] mBuffer = new byte[MIN_READ_UNIT]; + private final File mTestFile; + private final Random mGenerator; + private RandomAccessFile mAccessFile; + private boolean mHasPendingTune; + private long mReadStartMs; + private long mTotalReadBytes; + private long mInitialSkipMs; + private boolean mPacketMissing; + private boolean mEnableArtificialDelay; + + FileTunerHal(Context context, File testFile) { + super(context); + mTestFile = testFile; + mGenerator = Utils.createTestRandom(); + } + + /** + * Skip Initial parts of the TS file in order to start from specified time position. + * + * @param initialSkipMs initial position from where TS stream should be provided + */ + void setInitialSkipMs(long initialSkipMs) { + mInitialSkipMs = initialSkipMs; + } + + @Override + protected boolean openFirstAvailable() { + sIsDeviceOpen = true; + getDeliverySystemTypeFromDevice(); + return true; + } + + @Override + public void close() {} + + @Override + protected boolean isDeviceOpen() { + return sIsDeviceOpen; + } + + @Override + protected long getDeviceId() { + return DEVICE_ID; + } + + @Override + protected void nativeFinalize(long deviceId) { + if (deviceId != DEVICE_ID) { + return; + } + mPids.clear(); + } + + @Override + protected boolean nativeTune( + long deviceId, int frequency, @ModulationType String modulation, int timeoutMs) { + if (deviceId != DEVICE_ID) { + return false; + } + if (mHasPendingTune) { + return false; + } + closeInputStream(); + openInputStream(); + if (mAccessFile == null) { + return false; + } + + // Sleeping to simulate calling FE_GET_INFO and FE_SET_FRONTEND. + if (mEnableArtificialDelay) { + SystemClock.sleep(DELAY_IOCTL_SET_FRONTEND); + } + if (mHasPendingTune) { + return false; + } + + // Sleeping to simulate waiting frontend locked. + if (mEnableArtificialDelay) { + SystemClock.sleep(DELAY_IOCTL_WAIT_FRONTEND_LOCKED); + } + if (mHasPendingTune) { + return false; + } + mTotalReadBytes = 0; + mReadStartMs = System.currentTimeMillis(); + return true; + } + + @Override + protected void nativeAddPidFilter(long deviceId, int pid, @FilterType int filterType) { + if (deviceId != DEVICE_ID) { + return; + } + mPids.put(pid, true); + } + + @Override + protected void nativeCloseAllPidFilters(long deviceId) { + if (deviceId != DEVICE_ID) { + return; + } + mPids.clear(); + } + + @Override + protected void nativeStopTune(long deviceId) { + if (deviceId != DEVICE_ID) { + return; + } + mPids.clear(); + } + + @Override + protected int nativeWriteInBuffer(long deviceId, byte[] javaBuffer, int javaBufferSize) { + if (deviceId != DEVICE_ID) { + return 0; + } + if (mEnableArtificialDelay) { + long estimatedReadBytes = + (long) (TS_READ_BYTES_PER_MS * (System.currentTimeMillis() - mReadStartMs)) + + INITIAL_BUFFERED_TS_BYTES; + if (estimatedReadBytes < mTotalReadBytes) { + return 0; + } + } + int readSize = readInternal(); + if (readSize <= 0) { + closeInputStream(); + openInputStream(); + if (mAccessFile == null) { + return -1; + } + readSize = readInternal(); + } else { + mTotalReadBytes += readSize; + } + + if (mBuffer[0] != TS_SYNC_BYTE) { + return -1; + } + int filteredSize = 0; + javaBufferSize = (javaBufferSize / TS_PACKET_SIZE) * TS_PACKET_SIZE; + javaBufferSize = (javaBufferSize < TS_PAYLOAD_SIZE) ? javaBufferSize : TS_PAYLOAD_SIZE; + for (int i = 0, destPos = 0; + i < readSize && destPos + TS_PACKET_SIZE <= javaBufferSize; + i += TS_PACKET_SIZE) { + if (mBuffer[i] == TS_SYNC_BYTE) { + int pid = ((mBuffer[i + 1] & 0x1f) << 8) + (mBuffer[i + 2] & 0xff); + if (mPids.get(pid)) { + System.arraycopy(mBuffer, i, javaBuffer, destPos, TS_PACKET_SIZE); + destPos += TS_PACKET_SIZE; + filteredSize += TS_PACKET_SIZE; + } + } + } + return filteredSize; + } + + @Override + protected void nativeSetHasPendingTune(long deviceId, boolean hasPendingTune) { + if (deviceId != DEVICE_ID) { + return; + } + mHasPendingTune = hasPendingTune; + } + + @Override + protected int nativeGetDeliverySystemType(long deviceId) { + return DELIVERY_SYSTEM_ATSC; + } + + private int readInternal() { + int readSize; + try { + if (mPacketMissing) { + mAccessFile.skipBytes( + mGenerator.nextInt(TS_PACKET_COUNT_PER_PAYLOAD) * TS_PACKET_SIZE); + } + readSize = mAccessFile.read(mBuffer, 0, mBuffer.length); + } catch (IOException e) { + return -1; + } + return readSize; + } + + private void closeInputStream() { + try { + if (mAccessFile != null) { + mAccessFile.close(); + } + } catch (IOException e) { + } + mAccessFile = null; + } + + private void openInputStream() { + try { + mAccessFile = new RandomAccessFile(mTestFile, "r"); + + // Since sync frames are located once per 2 seconds, test with various + // starting offsets according to mInitialSkipMs. + long skipBytes = (long) (mInitialSkipMs * TS_READ_BYTES_PER_MS); + skipBytes = skipBytes / TS_PACKET_SIZE * TS_PACKET_SIZE; + mAccessFile.seek(skipBytes); + } catch (IOException e) { + Log.i(TAG, "open input stream failed:" + e); + } + } + + /** Gets the number of built-in tuner devices. Always 1 in this case. */ + public static int getNumberOfDevices(Context context) { + return 1; + } + + public void setEnablePacketMissing(boolean packetMissing) { + mPacketMissing = packetMissing; + } + + public void setEnableArtificialDelay(boolean enableArtificialDelay) { + mEnableArtificialDelay = enableArtificialDelay; + } +} diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/ZappingTimeTest.java b/tuner/tests/unittests/javatests/com/android/tv/tuner/ZappingTimeTest.java new file mode 100644 index 00000000..0e9bd357 --- /dev/null +++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/ZappingTimeTest.java @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2016 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.tuner; + +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.support.test.filters.LargeTest; +import android.test.InstrumentationTestCase; +import android.util.Log; +import android.view.Surface; +import com.android.tv.tuner.data.Cea708Data; +import com.android.tv.tuner.data.PsiData; +import com.android.tv.tuner.data.PsipData; +import com.android.tv.tuner.data.TunerChannel; +import com.android.tv.tuner.data.nano.Channel; +import com.android.tv.tuner.exoplayer.MpegTsPlayer; +import com.android.tv.tuner.exoplayer.MpegTsRendererBuilder; +import com.android.tv.tuner.exoplayer.buffer.BufferManager; +import com.android.tv.tuner.exoplayer.buffer.TrickplayStorageManager; +import com.android.tv.tuner.source.TsDataSourceManager; +import com.android.tv.tuner.tvinput.EventDetector; +import com.android.tv.tuner.tvinput.PlaybackBufferListener; +import com.google.android.exoplayer.ExoPlayer; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import org.junit.Ignore; + +/** This class use {@link FileTunerHal} to simulate tunerhal's actions to test zapping time. */ +@LargeTest +public class ZappingTimeTest extends InstrumentationTestCase { + private static final String TAG = "ZappingTimeTest"; + private static final boolean DEBUG = false; + private static final int TS_COPY_BUFFER_SIZE = 1024 * 512; + private static final int PROGRAM_NUMBER = 1; + private static final int VIDEO_PID = 49; + private static final int PCR_PID = 49; + private static final List<Integer> AUDIO_PIDS = Arrays.asList(51, 52, 53); + private static final long BUFFER_SIZE_DEF = 2 * 1024; + private static final int FREQUENCY = -1; + private static final String MODULATION = ""; + private static final long ZAPPING_TIME_OUT_MS = 10000; + private static final long MAX_AVERAGE_ZAPPING_TIME_MS = 4000; + private static final int TEST_ITERATION_COUNT = 10; + private static final int STRESS_ZAPPING_TEST_COUNT = 50; + private static final long SKIP_DURATION_MS_TO_ADD = 200; + private static final String TEST_TS_FILE_PATH = "capture_kqed.ts"; + + private static final int MSG_START_PLAYBACK = 1; + + private TunerChannel mChannel; + private FileTunerHal mTunerHal; + private MpegTsPlayer mPlayer; + private TsDataSourceManager mSourceManager; + private Handler mHandler; + private Context mTargetContext; + private File mTrickplayBufferDir; + private Surface mSurface; + private CountDownLatch mErrorLatch; + private CountDownLatch mDrawnToSurfaceLatch; + private CountDownLatch mWaitTuneExecuteLatch; + private AtomicLong mOnDrawnToSurfaceTimeMs = new AtomicLong(0); + private MockMpegTsPlayerListener mMpegTsPlayerListener = new MockMpegTsPlayerListener(); + private MockPlaybackBufferListener mPlaybackBufferListener = new MockPlaybackBufferListener(); + private MockEventListener mEventListener = new MockEventListener(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + mTargetContext = getInstrumentation().getTargetContext(); + mTrickplayBufferDir = mTargetContext.getCacheDir(); + HandlerThread handlerThread = new HandlerThread(TAG); + handlerThread.start(); + List<PsiData.PmtItem> pmtItems = new ArrayList<>(); + pmtItems.add(new PsiData.PmtItem(Channel.VideoStreamType.MPEG2, VIDEO_PID, null, null)); + for (int audioPid : AUDIO_PIDS) { + pmtItems.add( + new PsiData.PmtItem(Channel.AudioStreamType.A52AC3AUDIO, audioPid, null, null)); + } + + Context context = getInstrumentation().getContext(); + // Since assets and resource files are compressed, random access to the specified offset + // in assets or resource files will add some delay which is proportional to the offset. + // So the TS stream asset file are copied to a cache file, and the starting stream position + // in the file will be accessed by underlying {@link RandomAccessFile}. + File tsCacheFile = createCacheFile(context, mTargetContext, TEST_TS_FILE_PATH); + pmtItems.add(new PsiData.PmtItem(0x100, PCR_PID, null, null)); + mChannel = new TunerChannel(PROGRAM_NUMBER, pmtItems); + mChannel.setFrequency(FREQUENCY); + mChannel.setModulation(MODULATION); + mTunerHal = new FileTunerHal(context, tsCacheFile); + mTunerHal.openFirstAvailable(); + mSourceManager = TsDataSourceManager.createSourceManager(false); + mSourceManager.addTunerHalForTest(mTunerHal); + mHandler = + new Handler( + handlerThread.getLooper(), + new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_START_PLAYBACK: + { + mHandler.removeCallbacksAndMessages(null); + stopPlayback(); + mOnDrawnToSurfaceTimeMs.set(0); + mDrawnToSurfaceLatch = new CountDownLatch(1); + if (mWaitTuneExecuteLatch != null) { + mWaitTuneExecuteLatch.countDown(); + } + int frequency = msg.arg1; + boolean useSimpleSampleBuffer = (msg.arg2 == 1); + BufferManager bufferManager = null; + if (!useSimpleSampleBuffer) { + bufferManager = + new BufferManager( + new TrickplayStorageManager( + mTargetContext, + mTrickplayBufferDir, + 1024L + * 1024L + * BUFFER_SIZE_DEF)); + } + mChannel.setFrequency(frequency); + mSourceManager.setKeepTuneStatus(true); + mPlayer = + new MpegTsPlayer( + new MpegTsRendererBuilder( + mTargetContext, + bufferManager, + mPlaybackBufferListener), + mHandler, + mSourceManager, + null, + mMpegTsPlayerListener); + mPlayer.setCaptionServiceNumber( + Cea708Data.EMPTY_SERVICE_NUMBER); + mPlayer.prepare( + mTargetContext, + mChannel, + false, + mEventListener); + return true; + } + default: + { + Log.i(TAG, "Unhandled message code: " + msg.what); + return true; + } + } + } + }); + } + + @Override + protected void tearDown() throws Exception { + if (mPlayer != null) { + mPlayer.release(); + } + if (mSurface != null) { + mSurface.release(); + } + mHandler.getLooper().quitSafely(); + super.tearDown(); + } + + public void testZappingTime() { + zappingTimeTest(false, TEST_ITERATION_COUNT, true); + } + + public void testZappingTimeWithSimpleSampleBuffer() { + zappingTimeTest(true, TEST_ITERATION_COUNT, true); + } + + @Ignore("b/69978026") + @SuppressWarnings("JUnit4ClassUsedInJUnit3") + public void testStressZapping() { + zappingTimeTest(false, STRESS_ZAPPING_TEST_COUNT, false); + } + + @Ignore("b/69978093") + @SuppressWarnings("JUnit4ClassUsedInJUnit3") + public void testZappingWithPacketMissing() { + mTunerHal.setEnablePacketMissing(true); + mTunerHal.setEnableArtificialDelay(true); + SurfaceTexture surfaceTexture = new SurfaceTexture(0); + mSurface = new Surface(surfaceTexture); + long zappingStartTimeMs = System.currentTimeMillis(); + mErrorLatch = new CountDownLatch(1); + mHandler.obtainMessage(MSG_START_PLAYBACK, FREQUENCY, 0).sendToTarget(); + boolean errorAppeared = false; + while (System.currentTimeMillis() - zappingStartTimeMs < ZAPPING_TIME_OUT_MS) { + try { + errorAppeared = mErrorLatch.await(100, TimeUnit.MILLISECONDS); + if (errorAppeared) { + break; + } + } catch (InterruptedException e) { + } + } + assertFalse("Error happened when packet lost", errorAppeared); + } + + private static File createCacheFile(Context context, Context targetContext, String filename) + throws IOException { + File cacheFile = new File(targetContext.getCacheDir(), filename); + + if (cacheFile.createNewFile() == false) { + cacheFile.delete(); + cacheFile.createNewFile(); + } + + InputStream inputStream = context.getResources().getAssets().open(filename); + FileOutputStream fileOutputStream = new FileOutputStream(cacheFile); + + byte[] buffer = new byte[TS_COPY_BUFFER_SIZE]; + while (inputStream.read(buffer, 0, TS_COPY_BUFFER_SIZE) != -1) { + fileOutputStream.write(buffer); + } + + fileOutputStream.close(); + inputStream.close(); + + return cacheFile; + } + + private void zappingTimeTest( + boolean useSimpleSampleBuffer, int testIterationCount, boolean enableArtificialDelay) { + String bufferManagerLogString = + !enableArtificialDelay + ? "for stress test" + : useSimpleSampleBuffer ? "with simple sample buffer" : ""; + SurfaceTexture surfaceTexture = new SurfaceTexture(0); + mSurface = new Surface(surfaceTexture); + mTunerHal.setEnablePacketMissing(false); + mTunerHal.setEnableArtificialDelay(enableArtificialDelay); + double totalZappingTime = 0.0; + for (int i = 0; i < testIterationCount; i++) { + mWaitTuneExecuteLatch = new CountDownLatch(1); + long zappingStartTimeMs = System.currentTimeMillis(); + mTunerHal.setInitialSkipMs(SKIP_DURATION_MS_TO_ADD * (i % TEST_ITERATION_COUNT)); + mHandler.obtainMessage(MSG_START_PLAYBACK, FREQUENCY + i, useSimpleSampleBuffer ? 1 : 0) + .sendToTarget(); + try { + mWaitTuneExecuteLatch.await(); + } catch (InterruptedException e) { + } + boolean drawnToSurface = false; + while (System.currentTimeMillis() - zappingStartTimeMs < ZAPPING_TIME_OUT_MS) { + try { + drawnToSurface = mDrawnToSurfaceLatch.await(100, TimeUnit.MILLISECONDS); + if (drawnToSurface) { + break; + } + } catch (InterruptedException e) { + } + } + if (i == 0) { + continue; + // Get rid of the first result, which shows outlier often. + } + // In 10s, all zapping request will finish. Set the maximum zapping time as 10s could be + // reasonable. + totalZappingTime += + (mOnDrawnToSurfaceTimeMs.get() > 0 + ? mOnDrawnToSurfaceTimeMs.get() - zappingStartTimeMs + : ZAPPING_TIME_OUT_MS); + } + double averageZappingTime = totalZappingTime / (testIterationCount - 1); + Log.i(TAG, "Average zapping time " + bufferManagerLogString + ":" + averageZappingTime); + assertTrue( + "Average Zapping time " + + bufferManagerLogString + + " is too large:" + + averageZappingTime, + averageZappingTime < MAX_AVERAGE_ZAPPING_TIME_MS); + } + + private void stopPlayback() { + if (mPlayer != null) { + mPlayer.setPlayWhenReady(false); + mPlayer.release(); + mPlayer = null; + } + } + + private class MockMpegTsPlayerListener implements MpegTsPlayer.Listener { + + @Override + public void onStateChanged(boolean playWhenReady, int playbackState) { + if (DEBUG) { + Log.d(TAG, "ExoPlayer state change: " + playbackState + " " + playWhenReady); + } + if (playbackState == ExoPlayer.STATE_READY) { + mPlayer.setSurface(mSurface); + mPlayer.setPlayWhenReady(true); + mPlayer.setVolume(1.0f); + } + } + + @Override + public void onError(Exception e) { + if (DEBUG) { + Log.d(TAG, "onError"); + } + if (mErrorLatch != null) { + mErrorLatch.countDown(); + } + } + + @Override + public void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio) { + if (DEBUG) { + Log.d(TAG, "onVideoSizeChanged"); + } + } + + @Override + public void onDrawnToSurface(MpegTsPlayer player, Surface surface) { + if (DEBUG) { + Log.d(TAG, "onDrawnToSurface"); + } + mOnDrawnToSurfaceTimeMs.set(System.currentTimeMillis()); + if (mDrawnToSurfaceLatch != null) { + mDrawnToSurfaceLatch.countDown(); + } + } + + @Override + public void onAudioUnplayable() { + if (DEBUG) { + Log.d(TAG, "onAudioUnplayable"); + } + } + + @Override + public void onSmoothTrickplayForceStopped() { + if (DEBUG) { + Log.d(TAG, "onSmoothTrickplayForceStopped"); + } + } + } + + private static class MockPlaybackBufferListener implements PlaybackBufferListener { + @Override + public void onBufferStartTimeChanged(long startTimeMs) { + if (DEBUG) { + Log.d(TAG, "onBufferStartTimeChanged"); + } + } + + @Override + public void onBufferStateChanged(boolean available) { + if (DEBUG) { + Log.d(TAG, "onBufferStateChanged"); + } + } + + @Override + public void onDiskTooSlow() { + if (DEBUG) { + Log.d(TAG, "onDiskTooSlow"); + } + } + } + + private static class MockEventListener implements EventDetector.EventListener { + @Override + public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) { + if (DEBUG) { + Log.d(TAG, "onChannelDetected"); + } + } + + @Override + public void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items) { + if (DEBUG) { + Log.d(TAG, "onEventDetected"); + } + } + + @Override + public void onChannelScanDone() { + if (DEBUG) { + Log.d(TAG, "onChannelScanDone"); + } + } + } +} diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/AndroidManifest.xml b/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/AndroidManifest.xml new file mode 100644 index 00000000..3e6946a9 --- /dev/null +++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 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 + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.tv.tuner" + android:versionCode="1"> + <uses-sdk android:targetSdkVersion="26" android:minSdkVersion="23"/> + <application android:label="TunerTvInputLayoutTests" > + <activity android:name="com.android.tv.tuner.layout.tests.ScaledLayoutActivity" + android:label="ScaledLayout Test" /> + </application> +</manifest> diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/ScaledLayoutActivity.java b/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/ScaledLayoutActivity.java new file mode 100644 index 00000000..681465e1 --- /dev/null +++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/ScaledLayoutActivity.java @@ -0,0 +1,83 @@ +/* + * 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.tuner.layout.tests; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.widget.FrameLayout; +import com.android.tv.tuner.layout.ScaledLayout; + +public class ScaledLayoutActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_scaled_layout_test); + + FrameLayout frameLayout = findViewById(R.id.root_layout); + frameLayout.addView(createScaledLayoutTestcaseLayout()); + frameLayout.addView(createScaledLayoutTestcaseBounceY()); + } + + private ScaledLayout createScaledLayoutTestcaseLayout() { + ScaledLayout scaledLayout = new ScaledLayout(this); + scaledLayout.setLayoutParams(new FrameLayout.LayoutParams(100, 100)); + + View view1 = new View(this); + view1.setId(R.id.view1); + view1.setLayoutParams(new ScaledLayout.ScaledLayoutParams(0f, 0.5f, 0f, 0.5f)); + + View view2 = new View(this); + view2.setId(R.id.view2); + view2.setLayoutParams(new ScaledLayout.ScaledLayoutParams(0f, 0.5f, 0.5f, 1f)); + + View view3 = new View(this); + view3.setId(R.id.view3); + view3.setLayoutParams(new ScaledLayout.ScaledLayoutParams(0.5f, 1f, 0f, 0.5f)); + + View view4 = new View(this); + view4.setId(R.id.view4); + view4.setLayoutParams(new ScaledLayout.ScaledLayoutParams(0.5f, 1f, 0.5f, 1f)); + + scaledLayout.addView(view1); + scaledLayout.addView(view2); + scaledLayout.addView(view3); + scaledLayout.addView(view4); + + return scaledLayout; + } + + private ScaledLayout createScaledLayoutTestcaseBounceY() { + ScaledLayout scaledLayout = new ScaledLayout(this); + scaledLayout.setLayoutParams(new FrameLayout.LayoutParams(100, 100)); + + View view1 = new View(this); + view1.setId(R.id.view1); + view1.setLayoutParams(new ScaledLayout.ScaledLayoutParams(0.7f, 0.9f, 0f, 1f)); + + View view2 = new View(this); + view2.setId(R.id.view2); + view2.setLayoutParams(new ScaledLayout.ScaledLayoutParams(0.8f, 1f, 0f, 1f)); + + scaledLayout.addView(view1); + scaledLayout.addView(view2); + + return scaledLayout; + } +} diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/ScaledLayoutTest.java b/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/ScaledLayoutTest.java new file mode 100644 index 00000000..214b0631 --- /dev/null +++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/ScaledLayoutTest.java @@ -0,0 +1,147 @@ +/* + * 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.tuner.layout.tests; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; + +import android.content.Intent; +import android.support.test.filters.SmallTest; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.view.View; +import android.widget.FrameLayout; +import com.android.tv.tuner.layout.ScaledLayout; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ScaledLayoutTest { + + @Rule public ScaledActivityTestRule mActivityRule = new ScaledActivityTestRule(); + + @Before + public void setup() { + mActivityRule.launchActivity(new Intent()); + } + + @After + public void tearDown() { + mActivityRule.finishActivity(); + } + + @Test + public void testScaledLayout_layoutInXml() { + ScaledLayoutActivity activity = mActivityRule.getActivity(); + assertNotNull(activity); + FrameLayout rootLayout = mActivityRule.getActivity().findViewById(R.id.root_layout); + assertNotNull(rootLayout); + ScaledLayout scaledLayout = (ScaledLayout) rootLayout.getChildAt(0); + assertNotNull(scaledLayout); + View view1 = scaledLayout.findViewById(R.id.view1); + assertNotNull(view1); + View view2 = scaledLayout.findViewById(R.id.view2); + assertNotNull(view2); + View view3 = scaledLayout.findViewById(R.id.view3); + assertNotNull(view3); + View view4 = scaledLayout.findViewById(R.id.view4); + assertNotNull(view4); + assertEquals((int) (400 * 0.1), view1.getWidth()); + assertEquals((int) (300 * 0.2), view1.getHeight()); + assertEquals((int) (400 * 0.8), view1.getLeft()); + assertEquals((int) (300 * 0.1), view1.getTop()); + assertEquals((int) (400 * 0.1), view2.getWidth()); + assertEquals(300, view2.getHeight()); + assertEquals((int) (400 * 0.2), view2.getLeft()); + assertEquals(0, view2.getTop()); + assertEquals((int) (400 * 0.2), view3.getWidth()); + assertEquals((int) (300 * 0.1), view3.getHeight()); + assertEquals((int) (400 * 0.3), view3.getLeft()); + assertEquals((int) (300 * 0.4), view3.getTop()); + assertEquals((int) (400 * 0.1), view4.getWidth()); + assertEquals((int) (300 * 0.8), view4.getHeight()); + assertEquals((int) (400 * 0.05), view4.getLeft()); + assertEquals((int) (300 * 0.15), view4.getTop()); + } + + @Test + public void testScaledLayout_layoutThroughCode() { + ScaledLayoutActivity activity = mActivityRule.getActivity(); + assertNotNull(activity); + FrameLayout rootLayout = mActivityRule.getActivity().findViewById(R.id.root_layout); + assertNotNull(rootLayout); + ScaledLayout scaledLayout = (ScaledLayout) rootLayout.getChildAt(1); + assertNotNull(scaledLayout); + View view1 = scaledLayout.findViewById(R.id.view1); + assertNotNull(view1); + View view2 = scaledLayout.findViewById(R.id.view2); + assertNotNull(view2); + View view3 = scaledLayout.findViewById(R.id.view3); + assertNotNull(view3); + View view4 = scaledLayout.findViewById(R.id.view4); + assertNotNull(view4); + assertEquals(50, view1.getWidth()); + assertEquals(50, view1.getHeight()); + assertEquals(0, view1.getLeft()); + assertEquals(0, view1.getTop()); + assertEquals(50, view2.getWidth()); + assertEquals(50, view2.getHeight()); + assertEquals(50, view2.getLeft()); + assertEquals(0, view2.getTop()); + assertEquals(50, view3.getWidth()); + assertEquals(50, view3.getHeight()); + assertEquals(0, view3.getLeft()); + assertEquals(50, view3.getTop()); + assertEquals(50, view4.getWidth()); + assertEquals(50, view4.getHeight()); + assertEquals(50, view4.getLeft()); + assertEquals(50, view4.getTop()); + } + + @Test + public void testScaledLayout_bounceY() { + ScaledLayoutActivity activity = mActivityRule.getActivity(); + assertNotNull(activity); + FrameLayout rootLayout = mActivityRule.getActivity().findViewById(R.id.root_layout); + assertNotNull(rootLayout); + ScaledLayout scaledLayout = (ScaledLayout) rootLayout.getChildAt(2); + assertNotNull(scaledLayout); + View view1 = scaledLayout.findViewById(R.id.view1); + assertNotNull(view1); + View view2 = scaledLayout.findViewById(R.id.view2); + assertNotNull(view2); + assertEquals(100, view1.getWidth()); + assertEquals(20, view1.getHeight()); + assertEquals(0, view1.getLeft()); + assertEquals(60, view1.getTop()); + assertEquals(100, view2.getWidth()); + assertEquals(20, view2.getHeight()); + assertEquals(0, view2.getLeft()); + assertEquals(80, view2.getTop()); + } + + private static class ScaledActivityTestRule extends ActivityTestRule<ScaledLayoutActivity> { + + public ScaledActivityTestRule() { + super(ScaledLayoutActivity.class); + } + } +} diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/setup/TunerHalFactoryTest.java b/tuner/tests/unittests/javatests/com/android/tv/tuner/setup/TunerHalFactoryTest.java new file mode 100644 index 00000000..2354c827 --- /dev/null +++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/setup/TunerHalFactoryTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 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.tuner.setup; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import android.os.AsyncTask; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import com.android.tv.tuner.TunerHal; +import com.android.tv.tuner.setup.BaseTunerSetupActivity.TunerHalFactory; +import java.util.concurrent.Executor; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for {@link TunerHalFactory}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TunerHalFactoryTest { + private final FakeExecutor mFakeExecutor = new FakeExecutor(); + + private static class TestTunerHalFactory extends TunerHalFactory { + private TestTunerHalFactory(Executor executor) { + super(null, executor); + } + + @Override + protected TunerHal createInstance() { + return new com.android.tv.tuner.FakeTunerHal() {}; + } + } + + private static class FakeExecutor implements Executor { + Runnable mCommand; + + @Override + public synchronized void execute(final Runnable command) { + mCommand = command; + } + + private synchronized void executeActually() { + mCommand.run(); + } + } + + @Test + public void test_asyncGet() { + TunerHalFactory tunerHalFactory = new TestTunerHalFactory(mFakeExecutor); + assertNull(tunerHalFactory.mTunerHal); + tunerHalFactory.generate(); + assertNull(tunerHalFactory.mTunerHal); + mFakeExecutor.executeActually(); + TunerHal tunerHal = tunerHalFactory.getOrCreate(); + assertNotNull(tunerHal); + assertSame(tunerHal, tunerHalFactory.getOrCreate()); + tunerHalFactory.clear(); + } + + @Test + public void test_syncGet() { + TunerHalFactory tunerHalFactory = new TestTunerHalFactory(AsyncTask.SERIAL_EXECUTOR); + assertNull(tunerHalFactory.mTunerHal); + tunerHalFactory.generate(); + assertNotNull(tunerHalFactory.getOrCreate()); + } + + @Test + public void test_syncGetWithoutGenerate() { + TunerHalFactory tunerHalFactory = new TestTunerHalFactory(mFakeExecutor); + assertNull(tunerHalFactory.mTunerHal); + assertNotNull(tunerHalFactory.getOrCreate()); + } +} |