aboutsummaryrefslogtreecommitdiff
path: root/tuner/tests/unittests/javatests/com/android/tv/tuner
diff options
context:
space:
mode:
Diffstat (limited to 'tuner/tests/unittests/javatests/com/android/tv/tuner')
-rw-r--r--tuner/tests/unittests/javatests/com/android/tv/tuner/AndroidManifest.xml31
-rw-r--r--tuner/tests/unittests/javatests/com/android/tv/tuner/FakeTunerHal.java79
-rw-r--r--tuner/tests/unittests/javatests/com/android/tv/tuner/FileTunerHal.java274
-rw-r--r--tuner/tests/unittests/javatests/com/android/tv/tuner/ZappingTimeTest.java413
-rw-r--r--tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/AndroidManifest.xml25
-rw-r--r--tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/ScaledLayoutActivity.java83
-rw-r--r--tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/ScaledLayoutTest.java147
-rw-r--r--tuner/tests/unittests/javatests/com/android/tv/tuner/setup/TunerHalFactoryTest.java89
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());
+ }
+}