Tests for media classes accepting MediaDataSources.

Bug: 19429378
Change-Id: Ie734b51819785047c6596fd4d20eaf43c5d60a4b
diff --git a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
new file mode 100644
index 0000000..7ca498f
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 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 android.media.cts;
+
+import com.android.cts.media.R;
+
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.media.MediaDataSource;
+import android.media.MediaExtractor;
+import android.test.AndroidTestCase;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class MediaExtractorTest extends AndroidTestCase {
+    protected Resources mResources;
+    protected MediaExtractor mExtractor;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mResources = getContext().getResources();
+        mExtractor = new MediaExtractor();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mExtractor.release();
+    }
+
+    protected TestMediaDataSource getDataSourceFor(int resid) throws Exception {
+        AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
+        return TestMediaDataSource.fromAssetFd(afd);
+    }
+
+    protected TestMediaDataSource setDataSource(int resid) throws Exception {
+        TestMediaDataSource ds = getDataSourceFor(resid);
+        mExtractor.setDataSource(ds);
+        return ds;
+    }
+
+    public void testNullMediaDataSourceIsRejected() throws Exception {
+        try {
+            mExtractor.setDataSource((MediaDataSource)null);
+            fail("Expected IllegalArgumentException.");
+        } catch (IllegalArgumentException ex) {
+            // Expected, test passed.
+        }
+    }
+
+    public void testMediaDataSourceIsClosedOnRelease() throws Exception {
+        TestMediaDataSource dataSource = setDataSource(R.raw.testvideo);
+        mExtractor.release();
+        assertTrue(dataSource.isClosed());
+    }
+
+    public void testExtractorFailsIfMediaDataSourceThrows() throws Exception {
+        TestMediaDataSource dataSource = getDataSourceFor(R.raw.testvideo);
+        dataSource.throwFromReadAt();
+        try {
+            mExtractor.setDataSource(dataSource);
+            fail("Expected IOException.");
+        } catch (IOException e) {
+            // Expected.
+        }
+    }
+
+    public void testExtractorFailsIfMediaDataSourceReturnsAnError() throws Exception {
+        TestMediaDataSource dataSource = getDataSourceFor(R.raw.testvideo);
+        dataSource.returnFromReadAt(-1);
+        try {
+            mExtractor.setDataSource(dataSource);
+            fail("Expected IOException.");
+        } catch (IOException e) {
+            // Expected.
+        }
+    }
+
+    // Smoke test MediaExtractor reading from a DataSource.
+    public void testExtractFromAMediaDataSource() throws Exception {
+        TestMediaDataSource dataSource = setDataSource(R.raw.testvideo);
+        // 1MB is enough for any sample.
+        final ByteBuffer buf = ByteBuffer.allocate(1024*1024);
+        final int trackCount = mExtractor.getTrackCount();
+
+        for (int i = 0; i < trackCount; i++) {
+            mExtractor.selectTrack(i);
+        }
+
+        for (int i = 0; i < trackCount; i++) {
+            assertTrue(mExtractor.readSampleData(buf, 0) > 0);
+            assertTrue(mExtractor.advance());
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index 30c8370..622c0ec 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -20,102 +20,133 @@
 
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
+import android.media.MediaDataSource;
 import android.media.MediaMetadataRetriever;
 import android.test.AndroidTestCase;
 
 public class MediaMetadataRetrieverTest extends AndroidTestCase {
+    protected Resources mResources;
+    protected MediaMetadataRetriever mRetriever;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        mResources = getContext().getResources();
+        mRetriever = new MediaMetadataRetriever();
     }
 
     @Override
     protected void tearDown() throws Exception {
         super.tearDown();
+        mRetriever.release();
     }
 
-    public void test3gppMetadata() {
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-
+    protected void setDataSourceFd(int resid) {
         try {
-            Resources resources = getContext().getResources();
-            AssetFileDescriptor afd = resources.openRawResourceFd(R.raw.testvideo);
-
-            retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-
+            AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
+            mRetriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
             afd.close();
         } catch (Exception e) {
             fail("Unable to open file");
         }
+    }
+
+    protected TestMediaDataSource setDataSourceCallback(int resid) {
+        TestMediaDataSource ds = null;
+        try {
+            AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
+            ds = TestMediaDataSource.fromAssetFd(afd);
+            mRetriever.setDataSource(ds);
+        } catch (Exception e) {
+            fail("Unable to open file");
+        }
+        return ds;
+    }
+
+    public void test3gppMetadata() {
+        setDataSourceCallback(R.raw.testvideo);
 
         assertEquals("Title was other than expected",
-                "Title", retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
+                "Title", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
 
         assertEquals("Artist was other than expected",
                 "UTF16LE エンディアン ",
-                retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST));
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST));
 
         assertEquals("Album was other than expected",
                 "Test album",
-                retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
 
         assertEquals("Track number was other than expected",
                 "10",
-                retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER));
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER));
 
         assertEquals("Year was other than expected",
-                "2013", retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
+                "2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
 
         assertNull("Writer was unexpected present",
-                retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
     }
 
-    public void testSetDataSourceNull() {
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+    public void testID3v2Metadata() {
+        setDataSourceFd(R.raw.video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz_id3v2);
 
+        assertEquals("Title was other than expected",
+                "Title", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
+
+        assertEquals("Artist was other than expected",
+                "UTF16LE エンディアン ",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST));
+
+        assertEquals("Album was other than expected",
+                "Test album",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
+
+        assertEquals("Track number was other than expected",
+                "10",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER));
+
+        assertEquals("Year was other than expected",
+                "2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
+
+        assertNull("Writer was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
+    }
+
+    public void testSetDataSourceNullPath() {
         try {
-            retriever.setDataSource((String)null);
+            mRetriever.setDataSource((String)null);
             fail("Expected IllegalArgumentException.");
         } catch (IllegalArgumentException ex) {
             // Expected, test passed.
         }
     }
 
-    public void testID3v2Metadata() {
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-
+    public void testNullMediaDataSourceIsRejected() {
         try {
-            Resources resources = getContext().getResources();
-            AssetFileDescriptor afd = resources.openRawResourceFd(
-                    R.raw.video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz_id3v2);
-
-            retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-
-            afd.close();
-        } catch (Exception e) {
-            fail("Unable to open file");
+            mRetriever.setDataSource((MediaDataSource)null);
+            fail("Expected IllegalArgumentException.");
+        } catch (IllegalArgumentException ex) {
+            // Expected, test passed.
         }
+    }
 
-        assertEquals("Title was other than expected",
-                "Title", retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
+    public void testMediaDataSourceIsClosedOnRelease() throws Exception {
+        TestMediaDataSource dataSource = setDataSourceCallback(R.raw.testvideo);
+        mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
+        mRetriever.release();
+        assertTrue(dataSource.isClosed());
+    }
 
-        assertEquals("Artist was other than expected",
-                "UTF16LE エンディアン ",
-                retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST));
+    public void testRetrieveFailsIfMediaDataSourceThrows() throws Exception {
+        TestMediaDataSource dataSource = setDataSourceCallback(R.raw.testvideo);
+        dataSource.throwFromReadAt();
+        assertTrue(mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE) == null);
+    }
 
-        assertEquals("Album was other than expected",
-                "Test album",
-                retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
-
-        assertEquals("Track number was other than expected",
-                "10",
-                retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER));
-
-        assertEquals("Year was other than expected",
-                "2013", retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
-
-        assertNull("Writer was unexpectedly present",
-                retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
+    public void testRetrieveFailsIfMediaDataSourceReturnsAnError() throws Exception {
+        TestMediaDataSource dataSource = setDataSourceCallback(R.raw.testvideo);
+        dataSource.returnFromReadAt(-1);
+        assertTrue(mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE) == null);
     }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index 47467ac..44d0841 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -23,6 +23,7 @@
 import android.cts.util.MediaUtils;
 import android.media.AudioManager;
 import android.media.MediaCodec;
+import android.media.MediaDataSource;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.media.MediaPlayer;
@@ -117,10 +118,10 @@
         }
     }
 
-    public void testPlayNullSource() throws Exception {
+    public void testPlayNullSourcePath() throws Exception {
         try {
             mMediaPlayer.setDataSource((String) null);
-            fail("Null URI was accepted");
+            fail("Null path was accepted");
         } catch (RuntimeException e) {
             // expected
         }
@@ -1492,4 +1493,94 @@
         return getActivity().getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_MICROPHONE);
     }
+
+    // Smoke test playback from a MediaDataSource.
+    public void testPlaybackFromAMediaDataSource() throws Exception {
+        final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
+        final int duration = 10000;
+
+        if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
+            return;
+        }
+
+        TestMediaDataSource dataSource =
+                TestMediaDataSource.fromAssetFd(mResources.openRawResourceFd(resid));
+        // Test returning -1 from getSize() to indicate unknown size.
+        dataSource.returnFromGetSize(-1);
+        mMediaPlayer.setDataSource(dataSource);
+        playLoadedVideo(null, null, -1);
+        assertTrue(mMediaPlayer.isPlaying());
+
+        // Test pause and restart.
+        mMediaPlayer.pause();
+        Thread.sleep(SLEEP_TIME);
+        assertFalse(mMediaPlayer.isPlaying());
+        mMediaPlayer.start();
+        assertTrue(mMediaPlayer.isPlaying());
+
+        // Test reset.
+        mMediaPlayer.stop();
+        mMediaPlayer.reset();
+        mMediaPlayer.setDataSource(dataSource);
+        mMediaPlayer.prepare();
+        mMediaPlayer.start();
+        assertTrue(mMediaPlayer.isPlaying());
+
+        // Test seek. Note: the seek position is cached and returned as the
+        // current position so there's no point in comparing them.
+        mMediaPlayer.seekTo(duration - SLEEP_TIME);
+        while (mMediaPlayer.isPlaying()) {
+            Thread.sleep(SLEEP_TIME);
+        }
+    }
+
+    public void testNullMediaDataSourceIsRejected() throws Exception {
+        try {
+            mMediaPlayer.setDataSource((MediaDataSource) null);
+            fail("Null MediaDataSource was accepted");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    public void testMediaDataSourceIsClosedOnReset() throws Exception {
+        TestMediaDataSource dataSource = new TestMediaDataSource(new byte[0]);
+        mMediaPlayer.setDataSource(dataSource);
+        mMediaPlayer.reset();
+        assertTrue(dataSource.isClosed());
+    }
+
+    public void testPlaybackFailsIfMediaDataSourceThrows() throws Exception {
+        final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
+        if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
+            return;
+        }
+
+        setOnErrorListener();
+        TestMediaDataSource dataSource =
+                TestMediaDataSource.fromAssetFd(mResources.openRawResourceFd(resid));
+        mMediaPlayer.setDataSource(dataSource);
+        mMediaPlayer.prepare();
+
+        dataSource.throwFromReadAt();
+        mMediaPlayer.start();
+        assertTrue(mOnErrorCalled.waitForSignal());
+    }
+
+    public void testPlaybackFailsIfMediaDataSourceReturnsAnError() throws Exception {
+        final int resid = R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
+        if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
+            return;
+        }
+
+        setOnErrorListener();
+        TestMediaDataSource dataSource =
+                TestMediaDataSource.fromAssetFd(mResources.openRawResourceFd(resid));
+        mMediaPlayer.setDataSource(dataSource);
+        mMediaPlayer.prepare();
+
+        dataSource.returnFromReadAt(-1);
+        mMediaPlayer.start();
+        assertTrue(mOnErrorCalled.waitForSignal());
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java b/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java
index b7283db..95cb43c 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java
@@ -303,4 +303,14 @@
     public boolean checkTv() {
         return MediaUtils.check(isTv(), "not a TV");
     }
+
+    protected void setOnErrorListener() {
+        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+            @Override
+            public boolean onError(MediaPlayer mp, int what, int extra) {
+                mOnErrorCalled.signal();
+                return false;
+            }
+        });
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/TestMediaDataSource.java b/tests/tests/media/src/android/media/cts/TestMediaDataSource.java
new file mode 100644
index 0000000..87b4c59
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/TestMediaDataSource.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 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 android.media.cts;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.cts.MediaPlayerTestBase.Monitor;
+import android.media.MediaDataSource;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * A MediaDataSource that reads from a byte array for use in tests.
+ */
+public class TestMediaDataSource implements MediaDataSource {
+    private static final String TAG = "TestMediaDataSource";
+
+    private byte[] mData;
+
+    private boolean mThrowFromReadAt;
+    private boolean mThrowFromGetSize;
+    private Integer mReturnFromReadAt;
+    private Long mReturnFromGetSize;
+    private boolean mIsClosed;
+
+    // Read an asset fd into a new byte array data source. Closes afd.
+    public static TestMediaDataSource fromAssetFd(AssetFileDescriptor afd) throws IOException {
+        try {
+            InputStream in = afd.createInputStream();
+            final int size = (int) afd.getDeclaredLength();
+            byte[] data = new byte[(int) size];
+            int writeIndex = 0;
+            int numRead = 0;
+            do {
+                numRead = in.read(data, writeIndex, size - writeIndex);
+                writeIndex += numRead;
+            } while (numRead >= 0);
+            return new TestMediaDataSource(data);
+        } finally {
+            afd.close();
+        }
+    }
+
+    public TestMediaDataSource(byte[] data) {
+        mData = data;
+    }
+
+    @Override
+    public synchronized int readAt(long offset, byte[] buffer, int size) {
+        if (mThrowFromReadAt) {
+            throw new RuntimeException("Test exception from readAt()");
+        }
+        if (mReturnFromReadAt != null) {
+            return mReturnFromReadAt;
+        }
+
+        // Clamp reads past the end of the source.
+        if (offset >= mData.length) {
+            return 0;
+        }
+        if (offset + size > mData.length) {
+            size -= (offset + size) - mData.length;
+        }
+        System.arraycopy(mData, (int)offset, buffer, 0, size);
+        return size;
+    }
+
+    @Override
+    public synchronized long getSize() {
+        if (mThrowFromGetSize) {
+            throw new RuntimeException("Test exception from getSize()");
+        }
+        if (mReturnFromGetSize != null) {
+            return mReturnFromGetSize;
+        }
+
+        Log.v(TAG, "getSize: " + mData.length);
+        return mData.length;
+    }
+
+    // Note: it's fine to keep using this data source after closing it.
+    @Override
+    public synchronized void close() {
+        Log.v(TAG, "close()");
+        mIsClosed = true;
+    }
+
+    // Whether close() has been called.
+    public synchronized boolean isClosed() {
+        return mIsClosed;
+    }
+
+    public void throwFromReadAt() {
+        mThrowFromReadAt = true;
+    }
+
+    public void throwFromGetSize() {
+        mThrowFromGetSize = true;
+    }
+
+    public void returnFromReadAt(int numRead) {
+        mReturnFromReadAt = numRead;
+    }
+
+    public void returnFromGetSize(long size) {
+        mReturnFromGetSize = size;
+    }
+}
+