Backport MediaCodec-based test to StagefrighTest
am: 576904fc52

Change-Id: I29708ddb4b37d1ca7c9ec56318663a883328b849
diff --git a/tests/tests/security/res/raw/cve_2016_2507.mp4 b/tests/tests/security/res/raw/cve_2016_2507.mp4
new file mode 100644
index 0000000..ca248e1
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2016_2507.mp4
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index b45a697..b011306 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -24,12 +24,25 @@
 
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.graphics.SurfaceTexture;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
 import android.media.MediaPlayer;
+import android.opengl.GLES20;
+import android.opengl.GLES11Ext;
 import android.os.Looper;
+import android.os.SystemClock;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
+import android.view.Surface;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -48,6 +61,15 @@
     public StagefrightTest() {
     }
 
+    /***********************************************************
+     to prevent merge conflicts, add K tests below this comment,
+     before any existing test methods
+     ***********************************************************/
+
+    public void testStagefright_cve_2016_2507() throws Exception {
+        doStagefrightTest(R.raw.cve_2016_2507);
+    }
+
     public void testStagefright_cve_2015_1538_1() throws Exception {
         doStagefrightTest(R.raw.cve_2015_1538_1);
     }
@@ -125,58 +147,123 @@
     }
 
     private void doStagefrightTest(final int rid) throws Exception {
-        class MediaPlayerCrashListener
-                implements MediaPlayer.OnErrorListener,
-                    MediaPlayer.OnPreparedListener,
-                    MediaPlayer.OnCompletionListener {
+        doStagefrightTestMediaPlayer(rid);
+        doStagefrightTestMediaCodec(rid);
+    }
+
+    private Surface getDummySurface() {
+        int[] textures = new int[1];
+        GLES20.glGenTextures(1, textures, 0);
+        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]);
+        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+                GLES20.GL_TEXTURE_MIN_FILTER,
+                GLES20.GL_NEAREST);
+        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+                GLES20.GL_TEXTURE_MAG_FILTER,
+                GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+                GLES20.GL_TEXTURE_WRAP_S,
+                GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+                GLES20.GL_TEXTURE_WRAP_T,
+                GLES20.GL_CLAMP_TO_EDGE);
+        SurfaceTexture surfaceTex = new SurfaceTexture(textures[0]);
+        surfaceTex.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
             @Override
-            public boolean onError(MediaPlayer mp, int newWhat, int extra) {
+            public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+                Log.i(TAG, "new frame available");
+            }
+        });
+        return new Surface(surfaceTex);
+    }
+
+    class MediaPlayerCrashListener
+    implements MediaPlayer.OnErrorListener,
+        MediaPlayer.OnPreparedListener,
+        MediaPlayer.OnCompletionListener {
+        @Override
+        public boolean onError(MediaPlayer mp, int newWhat, int extra) {
+            Log.i(TAG, "error: " + newWhat + "/" + extra);
+            // don't overwrite a more severe error with a less severe one
+            if (what != MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
                 what = newWhat;
-                lock.lock();
-                condition.signal();
-                lock.unlock();
-
-                return true; // don't call oncompletion
             }
+            lock.lock();
+            condition.signal();
+            lock.unlock();
 
-            @Override
-            public void onPrepared(MediaPlayer mp) {
-                mp.start();
-            }
-
-            @Override
-            public void onCompletion(MediaPlayer mp) {
-                what = 0;
-                lock.lock();
-                condition.signal();
-                lock.unlock();
-            }
-
-            public int waitForError() throws InterruptedException {
-                lock.lock();
-                if (condition.awaitNanos(TIMEOUT_NS) <= 0) {
-                    Log.d(TAG, "timed out on waiting for error");
-                }
-                lock.unlock();
-                return what;
-            }
-
-            ReentrantLock lock = new ReentrantLock();
-            Condition condition = lock.newCondition();
-            int what;
+            return true; // don't call oncompletion
         }
 
+        @Override
+        public void onPrepared(MediaPlayer mp) {
+            mp.start();
+        }
+
+        @Override
+        public void onCompletion(MediaPlayer mp) {
+            // preserve error condition, if any
+            lock.lock();
+            condition.signal();
+            lock.unlock();
+        }
+
+        public int waitForError() throws InterruptedException {
+            lock.lock();
+            if (condition.awaitNanos(TIMEOUT_NS) <= 0) {
+                Log.d(TAG, "timed out on waiting for error");
+            }
+            lock.unlock();
+            if (what != 0) {
+                // Sometimes mediaserver signals a decoding error first, and *then* crashes
+                // due to additional in-flight buffers being processed, so wait a little
+                // and see if more errors show up.
+                SystemClock.sleep(1000);
+            }
+            return what;
+        }
+
+        ReentrantLock lock = new ReentrantLock();
+        Condition condition = lock.newCondition();
+        int what;
+    }
+
+    class LooperThread extends Thread {
+        private Looper mLooper;
+
+        LooperThread(Runnable runner) {
+            super(runner);
+        }
+
+        @Override
+        public void run() {
+            Looper.prepare();
+            mLooper = Looper.myLooper();
+            super.run();
+        }
+
+        public void stopLooper() {
+            mLooper.quitSafely();
+        }
+    }
+
+    private void doStagefrightTestMediaPlayer(final int rid) throws Exception {
+
+        String name = getInstrumentation().getContext().getResources().getResourceEntryName(rid);
+        Log.i(TAG, "start mediaplayer test for: " + name);
+
         final MediaPlayerCrashListener mpcl = new MediaPlayerCrashListener();
 
-        Thread t = new Thread(new Runnable() {
+        LooperThread t = new LooperThread(new Runnable() {
             @Override
             public void run() {
-                Looper.prepare();
 
                 MediaPlayer mp = new MediaPlayer();
                 mp.setOnErrorListener(mpcl);
                 mp.setOnPreparedListener(mpcl);
                 mp.setOnCompletionListener(mpcl);
+                Surface surface = getDummySurface();
+                mp.setSurface(surface);
                 try {
                     AssetFileDescriptor fd = getInstrumentation().getContext().getResources()
                         .openRawResourceFd(rid);
@@ -195,10 +282,148 @@
         });
 
         t.start();
-        String name = getInstrumentation().getContext().getResources().getResourceEntryName(rid);
         String cve = name.replace("_", "-").toUpperCase();
         assertFalse("Device *IS* vulnerable to " + cve,
                     mpcl.waitForError() == MediaPlayer.MEDIA_ERROR_SERVER_DIED);
-        t.interrupt();
+        t.stopLooper();
+        t.join(); // wait for thread to exit so we're sure the player was released
+    }
+
+    private void doStagefrightTestMediaCodec(final int rid) throws Exception {
+
+        final MediaPlayerCrashListener mpcl = new MediaPlayerCrashListener();
+
+        LooperThread thr = new LooperThread(new Runnable() {
+            @Override
+            public void run() {
+
+                MediaPlayer mp = new MediaPlayer();
+                mp.setOnErrorListener(mpcl);
+                try {
+                    AssetFileDescriptor fd = getInstrumentation().getContext().getResources()
+                        .openRawResourceFd(R.raw.good);
+
+                    // the onErrorListener won't receive MEDIA_ERROR_SERVER_DIED until
+                    // setDataSource has been called
+                    mp.setDataSource(fd.getFileDescriptor(),
+                                     fd.getStartOffset(),
+                                     fd.getLength());
+                } catch (Exception e) {
+                    // this is a known-good file, so no failure should occur
+                    fail("setDataSource of known-good file failed");
+                }
+
+                synchronized(mpcl) {
+                    mpcl.notify();
+                }
+                Looper.loop();
+                mp.release();
+            }
+        });
+        thr.start();
+        // wait until the thread has initialized the MediaPlayer
+        synchronized(mpcl) {
+            mpcl.wait();
+        }
+
+        Resources resources =  getInstrumentation().getContext().getResources();
+        AssetFileDescriptor fd = resources.openRawResourceFd(rid);
+        MediaExtractor ex = new MediaExtractor();
+        try {
+            ex.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+        } catch (IOException e) {
+            // ignore
+        }
+        int numtracks = ex.getTrackCount();
+        String rname = resources.getResourceEntryName(rid);
+        Log.i(TAG, "start mediacodec test for: " + rname + ", which has " + numtracks + " tracks");
+        for (int t = 0; t < numtracks; t++) {
+            // find all the available decoders for this format
+            ArrayList<String> matchingCodecs = new ArrayList<String>();
+            MediaFormat format = null;
+            try {
+                format = ex.getTrackFormat(t);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "could not get track format for track " + t);
+                continue;
+            }
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            int numCodecs = MediaCodecList.getCodecCount();
+            for (int i = 0; i < numCodecs; i++) {
+                MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
+                if (info.isEncoder()) {
+                    continue;
+                }
+                try {
+                    MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                    if (caps != null) {
+                        matchingCodecs.add(info.getName());
+                    }
+                } catch (IllegalArgumentException e) {
+                    // type is not supported
+                }
+            }
+
+            if (matchingCodecs.size() == 0) {
+                Log.w(TAG, "no codecs for track " + t + ", type " + mime);
+            }
+            // decode this track once with each matching codec
+            ex.selectTrack(t);
+            for (String codecName: matchingCodecs) {
+                Log.i(TAG, "Decoding track " + t + " using codec " + codecName);
+                ex.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+                MediaCodec codec = MediaCodec.createByCodecName(codecName);
+                Surface surface = null;
+                if (mime.startsWith("video/")) {
+                    surface = getDummySurface();
+                }
+                try {
+                    codec.configure(format, surface, null, 0);
+                    codec.start();
+                } catch (Exception e) {
+                    Log.i(TAG, "Failed to start/configure:", e);
+                }
+                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+                try {
+                    ByteBuffer [] inputBuffers = codec.getInputBuffers();
+                    while (true) {
+                        int flags = ex.getSampleFlags();
+                        long time = ex.getSampleTime();
+                        int bufidx = codec.dequeueInputBuffer(5000);
+                        if (bufidx >= 0) {
+                            int n = ex.readSampleData(inputBuffers[bufidx], 0);
+                            if (n < 0) {
+                                flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                                time = 0;
+                                n = 0;
+                            }
+                            codec.queueInputBuffer(bufidx, 0, n, time, flags);
+                            ex.advance();
+                        }
+                        int status = codec.dequeueOutputBuffer(info, 5000);
+                        if (status >= 0) {
+                            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                                break;
+                            }
+                            if (info.presentationTimeUs > TIMEOUT_NS / 1000) {
+                                Log.d(TAG, "stopping after 10 seconds worth of data");
+                                break;
+                            }
+                            codec.releaseOutputBuffer(status, true);
+                        }
+                    }
+                } catch (Exception e) {
+                    // local exceptions ignored, not security issues
+                } finally {
+                    codec.release();
+                }
+            }
+        }
+        ex.release();
+        String cve = rname.replace("_", "-").toUpperCase();
+        assertFalse("Device *IS* vulnerable to " + cve,
+                    mpcl.waitForError() == MediaPlayer.MEDIA_ERROR_SERVER_DIED);
+        thr.stopLooper();
+        thr.join();
     }
 }