Merge "Update WebViewTest#testSetPictureListener."
diff --git a/tests/tests/holo/src/android/holo/cts/ThemeTestActivity.java b/tests/tests/holo/src/android/holo/cts/ThemeTestActivity.java
index 74d74dc..b7fdd1d 100644
--- a/tests/tests/holo/src/android/holo/cts/ThemeTestActivity.java
+++ b/tests/tests/holo/src/android/holo/cts/ThemeTestActivity.java
@@ -85,7 +85,8 @@
         int layoutIndex = getIntent().getIntExtra(EXTRA_LAYOUT_INDEX, -1);
         int adapterMode = getIntent().getIntExtra(EXTRA_LAYOUT_ADAPTER_MODE, -1);
 
-        Log.i(TAG, "Theme index: " + themeIndex + " Layout index: " + layoutIndex);
+        Log.i(TAG, "Theme index: " + themeIndex + " Layout index: " + layoutIndex +
+                " adapter mode: " + adapterMode);
 
         if (themeIndex < 0 && layoutIndex < 0) {
             mIterator = new AllThemesIterator(task, adapterMode);
diff --git a/tests/tests/media/res/raw/video_176x144_yv12.raw b/tests/tests/media/res/raw/video_176x144_yv12.raw
new file mode 100644
index 0000000..23af164
--- /dev/null
+++ b/tests/tests/media/res/raw/video_176x144_yv12.raw
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/IvfReader.java b/tests/tests/media/src/android/media/cts/IvfReader.java
new file mode 100644
index 0000000..508ae25
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/IvfReader.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2013 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 java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * A simple reader for an IVF file.
+ *
+ * IVF format is a simple container format for VP8 encoded frames.
+ * This reader is capable of getting frame count, width and height
+ * from the header, and access individual frames randomly by
+ * frame number.
+ */
+
+public class IvfReader {
+    private static final byte HEADER_END = 32;
+    private static final byte FOURCC_HEAD = 8;
+    private static final byte WIDTH_HEAD = 12;
+    private static final byte HEIGHT_HEAD = 14;
+    private static final byte FRAMECOUNT_HEAD = 24;
+    private static final byte FRAME_HEADER_SIZE = 12;
+
+    private RandomAccessFile mIvfFile;
+    private boolean mHeaderValid;
+    private int mWidth;
+    private int mHeight;
+    private int mFrameCount;
+    private int[] mFrameHeads;  // Head of frame header
+    private int[] mFrameSizes;  // Frame size excluding header
+
+
+    /**
+     * Initializes the IVF file reader.
+     *
+     * Only minimal verification is done to check if this
+     * is indeed a valid IVF file. (fourcc, signature)
+     *
+     * All frame headers are read in advance.
+     *
+     * @param filename   name of the IVF file
+     */
+    public IvfReader(String filename) throws IOException{
+        mIvfFile = new RandomAccessFile(filename, "r");
+
+        mHeaderValid = verifyHeader();
+        readHeaderData();
+        readFrameMetadata();
+    }
+
+    /**
+     * Tells if file header seems to be valid.
+     *
+     * Only minimal verification is done to check if this
+     * is indeed a valid IVF file. (fourcc, signature)
+     */
+    public boolean isHeaderValid(){
+        return mHeaderValid;
+    }
+
+    /**
+     * Returns frame width according to header information.
+     */
+    public int getWidth(){
+        return mWidth;
+    }
+
+    /**
+     * Returns frame height according to header information.
+     */
+    public int getHeight(){
+        return mHeight;
+    }
+
+    /**
+     * Returns frame count according to header information.
+     */
+    public int getFrameCount(){
+        return mFrameCount;
+    }
+
+    /**
+     * Returns frame data by index.
+     *
+     * @param frameIndex index of the frame to read, greater-equal
+     * than 0 and less than frameCount.
+     */
+    public byte[] readFrame(int frameIndex) throws IOException {
+        if (frameIndex > mFrameCount | frameIndex < 0){
+            return null;
+        }
+        int frameSize = mFrameSizes[frameIndex];
+        int frameHead = mFrameHeads[frameIndex];
+
+        byte[] frame = new byte[frameSize];
+        mIvfFile.seek(frameHead + FRAME_HEADER_SIZE);
+        mIvfFile.read(frame);
+
+        return frame;
+    }
+
+    /**
+     * Closes IVF file.
+     */
+    public void close() throws IOException{
+        mIvfFile.close();
+    }
+
+    private boolean verifyHeader() throws IOException{
+        mIvfFile.seek(0);
+
+        if (mIvfFile.length() < HEADER_END){
+            return false;
+        }
+
+        // DKIF signature
+        boolean signatureMatch = ((mIvfFile.readByte() == (byte)'D') &&
+                (mIvfFile.readByte() == (byte)'K') &&
+                (mIvfFile.readByte() == (byte)'I') &&
+                (mIvfFile.readByte() == (byte)'F'));
+
+        // Fourcc
+        mIvfFile.seek(FOURCC_HEAD);
+        boolean fourccMatch = ((mIvfFile.readByte() == (byte)'V') &&
+                (mIvfFile.readByte() == (byte)'P') &&
+                (mIvfFile.readByte() == (byte)'8') &&
+                (mIvfFile.readByte() == (byte)'0'));
+
+        return signatureMatch && fourccMatch;
+    }
+
+    private void readHeaderData() throws IOException{
+        // width
+        mIvfFile.seek(WIDTH_HEAD);
+        mWidth = (int) changeEndianness(mIvfFile.readShort());
+
+        // height
+        mIvfFile.seek(HEIGHT_HEAD);
+        mHeight = (int) changeEndianness(mIvfFile.readShort());
+
+        // frame count
+        mIvfFile.seek(FRAMECOUNT_HEAD);
+        mFrameCount = changeEndianness(mIvfFile.readInt());
+
+        // allocate frame metadata
+        mFrameHeads = new int[mFrameCount];
+        mFrameSizes = new int[mFrameCount];
+    }
+
+    private void readFrameMetadata() throws IOException{
+        int frameHead = HEADER_END;
+        for(int i = 0; i < mFrameCount; i++){
+            mIvfFile.seek(frameHead);
+            int frameSize = changeEndianness(mIvfFile.readInt());
+            mFrameHeads[i] = frameHead;
+            mFrameSizes[i] = frameSize;
+            // next frame
+            frameHead += FRAME_HEADER_SIZE + frameSize;
+        }
+    }
+
+    private static short changeEndianness(short value){
+        // Rationale for down-cast;
+        // Java Language specification 15.19:
+        //  "The type of the shift expression is the promoted type of the left-hand operand."
+        // Java Language specification 5.6:
+        //  "...if the operand is of compile-time type byte, short, or char,
+        //  unary numeric promotion promotes it to a value of type int by a widening conversion."
+        return (short) (((value << 8) & 0XFF00) | ((value >> 8) & 0X00FF));
+    }
+
+    private static int changeEndianness(int value){
+        return (((value << 24) & 0XFF000000) |
+                ((value << 8)  & 0X00FF0000) |
+                ((value >> 8)  & 0X0000FF00) |
+                ((value >> 24) & 0X000000FF));
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/IvfWriter.java b/tests/tests/media/src/android/media/cts/IvfWriter.java
new file mode 100644
index 0000000..ccc0ac5
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/IvfWriter.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2013 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 java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Writes an IVF file.
+ *
+ * IVF format is a simple container format for VP8 encoded frames.
+ */
+
+public class IvfWriter {
+    private static final byte HEADER_END = 32;
+    private RandomAccessFile mOutputFile;
+    private int mWidth;
+    private int mHeight;
+    private int mScale;
+    private int mRate;
+    private int mFrameCount;
+
+    /**
+     * Initializes the IVF file writer.
+     *
+     * Timebase fraction is in format scale/rate, e.g. 1/1000
+     * Timestamp values supplied while writing frames should be in accordance
+     * with this timebase value.
+     *
+     * @param filename   name of the IVF file
+     * @param width      frame width
+     * @param height     frame height
+     * @param scale      timebase scale (or numerator of the timebase fraction)
+     * @param rate       timebase rate (or denominator of the timebase fraction)
+     */
+    public IvfWriter(String filename,
+                     int width, int height,
+                     int scale, int rate) throws IOException {
+        mOutputFile = new RandomAccessFile(filename, "rw");
+        mWidth = width;
+        mHeight = height;
+        mScale = scale;
+        mRate = rate;
+        mFrameCount = 0;
+        mOutputFile.seek(HEADER_END);  // Skip the header for now, as framecount is unknown
+    }
+
+    /**
+     * Initializes the IVF file writer with a microsecond timebase.
+     *
+     *
+     * Microsecond timebase is default for OMX thus stagefright.
+     *
+     * @param filename   name of the IVF file
+     * @param width      frame width
+     * @param height     frame height
+     */
+    public IvfWriter(String filename, int width, int height) throws IOException {
+        this(filename, width, height, 1, 1000000);
+    }
+
+    /**
+     * Finalizes the IVF header and closes the file.
+     */
+    public void close() throws IOException{
+        // Write header now
+        mOutputFile.seek(0);
+        mOutputFile.write(makeIvfHeader(mFrameCount, mWidth, mHeight, mScale, mRate));
+        mOutputFile.close();
+    }
+
+    /**
+     * Writes a single encoded VP8 frame with its frame header.
+     *
+     * @param frame     actual contents of the encoded frame data
+     * @param width     timestamp of the frame (in accordance to specified timebase)
+     */
+    public void writeFrame(byte[] frame, long timeStamp) throws IOException {
+        mOutputFile.write(makeIvfFrameHeader(frame.length, timeStamp));
+        mOutputFile.write(frame);
+        mFrameCount++;
+    }
+
+    /**
+     * Makes a 32 byte file header for IVF format.
+     *
+     * Timebase fraction is in format scale/rate, e.g. 1/1000
+     *
+     * @param frameCount total number of frames file contains
+     * @param width      frame width
+     * @param height     frame height
+     * @param scale      timebase scale (or numerator of the timebase fraction)
+     * @param rate       timebase rate (or denominator of the timebase fraction)
+     */
+    private static byte[] makeIvfHeader(int frameCount, int width, int height, int scale, int rate){
+        byte[] ivfHeader = new byte[32];
+        ivfHeader[0] = 'D';
+        ivfHeader[1] = 'K';
+        ivfHeader[2] = 'I';
+        ivfHeader[3] = 'F';
+        lay16Bits(ivfHeader, 4, 0);  // version
+        lay16Bits(ivfHeader, 6, 32);  // header size
+        ivfHeader[8] = 'V';  // fourcc
+        ivfHeader[9] = 'P';
+        ivfHeader[10] = '8';
+        ivfHeader[11] = '0';
+        lay16Bits(ivfHeader, 12, width);
+        lay16Bits(ivfHeader, 14, height);
+        lay32Bits(ivfHeader, 16, rate);  // scale/rate
+        lay32Bits(ivfHeader, 20, scale);
+        lay32Bits(ivfHeader, 24, frameCount);
+        lay32Bits(ivfHeader, 28, 0);  // unused
+        return ivfHeader;
+    }
+
+    /**
+     * Makes a 12 byte header for an encoded frame.
+     *
+     * @param size      frame size
+     * @param timestamp presentation timestamp of the frame
+     */
+    private static byte[] makeIvfFrameHeader(int size, long timestamp){
+        byte[] frameHeader = new byte[12];
+        lay32Bits(frameHeader, 0, size);
+        lay64bits(frameHeader, 4, timestamp);
+        return frameHeader;
+    }
+
+
+    /**
+     * Lays least significant 16 bits of an int into 2 items of a byte array.
+     *
+     * Note that ordering is little-endian.
+     *
+     * @param array     the array to be modified
+     * @param index     index of the array to start laying down
+     * @param value     the integer to use least significant 16 bits
+     */
+    private static void lay16Bits(byte[] array, int index, int value){
+        array[index] = (byte) (value);
+        array[index + 1] = (byte) (value >> 8);
+    }
+
+    /**
+     * Lays an int into 4 items of a byte array.
+     *
+     * Note that ordering is little-endian.
+     *
+     * @param array     the array to be modified
+     * @param index     index of the array to start laying down
+     * @param value     the integer to use
+     */
+    private static void lay32Bits(byte[] array, int index, int value){
+        for (int i = 0; i < 4; i++){
+            array[index + i] = (byte) (value >> (i * 8));
+        }
+    }
+
+    /**
+     * Lays a long int into 8 items of a byte array.
+     *
+     * Note that ordering is little-endian.
+     *
+     * @param array     the array to be modified
+     * @param index     index of the array to start laying down
+     * @param value     the integer to use
+     */
+    private static void lay64bits(byte[] array, int index, long value){
+        for (int i = 0; i < 8; i++){
+            array[index + i] = (byte) (value >> (i * 8));
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
new file mode 100644
index 0000000..308fb98
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2013 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.Context;
+import android.content.res.Resources;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaFormat;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.cts.media.R;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * Basic verification test for vp8 encoder.
+ *
+ * A raw yv12 stream is encoded and written to an IVF
+ * file, which is later decoded by vp8 decoder to verify
+ * frames are at least decodable.
+ */
+public class Vp8EncoderTest extends AndroidTestCase {
+
+    private static final String TAG = "VP8EncoderTest";
+    private static final String VP8_MIME = "video/x-vnd.on2.vp8";
+    private static final String VPX_DECODER_NAME = "OMX.google.vpx.decoder";
+    private static final String VPX_ENCODER_NAME = "OMX.google.vpx.encoder";
+    private static final String BASIC_IVF = "video_176x144_vp8_basic.ivf";
+    private static final long DEFAULT_TIMEOUT_US = 5000;
+
+    private Resources mResources;
+    private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
+    private ByteBuffer[] mInputBuffers;
+    private ByteBuffer[] mOutputBuffers;
+
+    @Override
+    public void setContext(Context context) {
+        super.setContext(context);
+        mResources = mContext.getResources();
+    }
+
+    /**
+     * A basic test for VP8 encoder.
+     *
+     * Encodes a raw stream with default configuration options,
+     * and then decodes it to verify the bitstream.
+     */
+    public void testBasic() throws Exception {
+        encode(BASIC_IVF,
+               R.raw.video_176x144_yv12,
+               176,  // width
+               144,  // height
+               30);  // framerate
+        decode(BASIC_IVF);
+    }
+
+
+    /**
+     * A basic check if an encoded stream is decodable.
+     *
+     * The most basic confirmation we can get about a frame
+     * being properly encoded is trying to decode it.
+     * (Especially in realtime mode encode output is non-
+     * deterministic, therefore a more thorough check like
+     * md5 sum comparison wouldn't work.)
+     *
+     * Indeed, MediaCodec will raise an IllegalStateException
+     * whenever vp8 decoder fails to decode a frame, and
+     * this test uses that fact to verify the bitstream.
+     *
+     * @param filename  The name of the IVF file containing encoded bitsream.
+     */
+    private void decode(String filename) throws Exception {
+        IvfReader ivf = null;
+        try {
+            ivf = new IvfReader(filename);
+            int frameWidth = ivf.getWidth();
+            int frameHeight = ivf.getHeight();
+            int frameCount = ivf.getFrameCount();
+
+            assertTrue(frameWidth > 0);
+            assertTrue(frameHeight > 0);
+            assertTrue(frameCount > 0);
+
+            MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME,
+                                                               ivf.getWidth(),
+                                                               ivf.getHeight());
+
+            Log.d(TAG, "Creating decoder");
+            MediaCodec decoder = MediaCodec.createByCodecName(VPX_DECODER_NAME);
+            decoder.configure(format,
+                              null,  // surface
+                              null,  // crypto
+                              0);  // flags
+            decoder.start();
+
+            mInputBuffers = decoder.getInputBuffers();
+            mOutputBuffers = decoder.getOutputBuffers();
+
+            // decode loop
+            int frameIndex = 0;
+            boolean sawOutputEOS = false;
+            boolean sawInputEOS = false;
+
+            while (!sawOutputEOS) {
+                if (!sawInputEOS) {
+                    int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
+                    if (inputBufIndex >= 0) {
+                        byte[] frame = ivf.readFrame(frameIndex);
+
+                        if (frameIndex == frameCount - 1) {
+                            sawInputEOS = true;
+                        }
+
+                        mInputBuffers[inputBufIndex].clear();
+                        mInputBuffers[inputBufIndex].put(frame);
+                        mInputBuffers[inputBufIndex].rewind();
+
+                        Log.d(TAG, "Decoding frame at index " + frameIndex);
+                        try {
+                            decoder.queueInputBuffer(
+                                    inputBufIndex,
+                                    0,  // offset
+                                    frame.length,
+                                    frameIndex,
+                                    sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+                        } catch (IllegalStateException ise) {
+                            //That is all what is passed from MediaCodec in case of
+                            //decode failure.
+                            fail("Failed to decode frame at index " + frameIndex);
+                        }
+                        frameIndex++;
+                    }
+                }
+
+                int result = decoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US);
+                if (result >= 0) {
+                    int outputBufIndex = result;
+                    if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        sawOutputEOS = true;
+                    }
+                    decoder.releaseOutputBuffer(outputBufIndex, false);
+                } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    mOutputBuffers = decoder.getOutputBuffers();
+                }
+            }
+            decoder.stop();
+            decoder.release();
+        } finally {
+            if (ivf != null) {
+                ivf.close();
+            }
+        }
+    }
+
+    /**
+     * A basic vp8 encode loop.
+     *
+     * MediaCodec will raise an IllegalStateException
+     * whenever vp8 encoder fails to encode a frame.
+     *
+     * In addition to that written IVF file can be tested
+     * to be decodable in order to verify the bitstream produced.
+     *
+     * Color format of input file should be YUV420, and frameWidth,
+     * frameHeight should be supplied correctly as raw input file doesn't
+     * include any header data.
+     *
+     * @param outputFilename  The name of the IVF file to write encoded bitsream
+     * @param rawInputFd      File descriptor for the raw input file (YUV420)
+     * @param frameWidth      Frame width of input file
+     * @param frameHeight     Frame height of input file
+     * @param frameRate       Frame rate of input file in frames per second
+     */
+    private void encode(String outputFilename, int rawInputFd,
+                       int frameWidth, int frameHeight, int frameRate) throws Exception {
+        int frameSize = frameWidth * frameHeight * 3 / 2;
+
+
+        // Create a media format signifying desired output
+        MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, 100000);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                          CodecCapabilities.COLOR_FormatYUV420Planar);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+
+        Log.d(TAG, "Creating encoder");
+        MediaCodec encoder;
+        encoder = MediaCodec.createByCodecName(VPX_ENCODER_NAME);
+        encoder.configure(format,
+                          null,  // surface
+                          null,  // crypto
+                          MediaCodec.CONFIGURE_FLAG_ENCODE);
+        encoder.start();
+
+        mInputBuffers = encoder.getInputBuffers();
+        mOutputBuffers = encoder.getOutputBuffers();
+
+        InputStream rawStream = null;
+        IvfWriter ivf = null;
+
+        try {
+            rawStream = mResources.openRawResource(rawInputFd);
+            ivf = new IvfWriter(outputFilename, frameWidth, frameHeight);
+            // encode loop
+            long presentationTimeUs = 0;
+            int frameIndex = 0;
+            boolean sawInputEOS = false;
+            boolean sawOutputEOS = false;
+
+            while (!sawOutputEOS) {
+                if (!sawInputEOS) {
+                    int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
+                    if (inputBufIndex >= 0) {
+                        byte[] frame = new byte[frameSize];
+                        int bytesRead = rawStream.read(frame);
+
+                        if (bytesRead == -1) {
+                            sawInputEOS = true;
+                            bytesRead = 0;
+                        }
+
+                        mInputBuffers[inputBufIndex].clear();
+                        mInputBuffers[inputBufIndex].put(frame);
+                        mInputBuffers[inputBufIndex].rewind();
+
+                        presentationTimeUs = (frameIndex * 1000000) / frameRate;
+                        Log.d(TAG, "Encoding frame at index " + frameIndex);
+                        encoder.queueInputBuffer(
+                                inputBufIndex,
+                                0,  // offset
+                                bytesRead,  // size
+                                presentationTimeUs,
+                                sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+                        frameIndex++;
+                    }
+                }
+
+                int result = encoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US);
+                if (result >= 0) {
+                    int outputBufIndex = result;
+                    byte[] buffer = new byte[mBufferInfo.size];
+                    mOutputBuffers[outputBufIndex].rewind();
+                    mOutputBuffers[outputBufIndex].get(buffer, 0, mBufferInfo.size);
+
+                    if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        sawOutputEOS = true;
+                    } else {
+                        ivf.writeFrame(buffer, mBufferInfo.presentationTimeUs);
+                    }
+                    encoder.releaseOutputBuffer(outputBufIndex,
+                                                false);  // render
+                } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    mOutputBuffers = encoder.getOutputBuffers();
+                }
+            }
+
+            encoder.stop();
+            encoder.release();
+        } finally {
+            if (ivf != null) {
+                ivf.close();
+            }
+
+            if (rawStream != null) {
+                rawStream.close();
+            }
+        }
+    }
+}
diff --git a/tests/tests/os/src/android/os/cts/UsbDebuggingTest.java b/tests/tests/os/src/android/os/cts/UsbDebuggingTest.java
new file mode 100644
index 0000000..60583e7
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/UsbDebuggingTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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.os.cts;
+
+import android.os.Build;
+import android.os.SystemProperties;
+import android.test.AndroidTestCase;
+import java.io.File;
+
+public class UsbDebuggingTest extends AndroidTestCase {
+
+    public void testUsbDebugging() {
+        // Secure USB debugging must be enabled
+        assertEquals("1", SystemProperties.get("ro.adb.secure"));
+
+        // Don't ship vendor keys in user build
+        if ("user".equals(Build.TYPE)) {
+            File keys = new File("/adb_keys");
+            assertFalse(keys.exists());
+        }
+    }
+
+}
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index 7fb2337..e7f02b5 100644
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -23,10 +23,12 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileFilter;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.io.FileReader;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -616,6 +618,34 @@
         return retval;
     }
 
+    /**
+     * Scan through /proc/self/mounts, looking for the /system line. If the line
+     * has "ro" in the 4th column, then we know the filesystem is mounted read-only.
+     */
+    public void testSystemMountedRO() throws IOException {
+        BufferedReader br = new BufferedReader(new FileReader("/proc/self/mounts"));
+        String line;
+        boolean foundSystem = false;
+        while((line = br.readLine()) != null) {
+            String[] fields = line.split(" ");
+            String mountPoint = fields[1];
+            if ("/system".equals(mountPoint)) {
+                foundSystem = true;
+                String all_options = fields[3];
+                boolean foundRo = false;
+                String[] options = all_options.split(",");
+                for (String option : options) {
+                    if ("ro".equals(option)) {
+                        foundRo = true;
+                        break;
+                    }
+                }
+                assertTrue(mountPoint + " is not mounted read-only", foundRo);
+            }
+        }
+        assertTrue("Cannot find /system partition", foundSystem);
+    }
+
     public void testAllBlockDevicesAreSecure() throws Exception {
         Set<File> insecure = getAllInsecureBlockDevicesInDirAndSubdir(new File("/dev"));
         assertTrue("Found insecure: " + insecure.toString(),
diff --git a/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java b/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
index 877a89e..f7e5443 100755
--- a/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
@@ -80,7 +80,8 @@
         "android.net.wifi.p2p.CONNECTION_STATE_CHANGE",
         "android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED",
         "android.net.conn.TETHER_STATE_CHANGED",
-        "android.net.conn.INET_CONDITION_ACTION"
+        "android.net.conn.INET_CONDITION_ACTION",
+        "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED"
     };
 
     /**
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/SamplerTest.java b/tests/tests/renderscript/src/android/renderscript/cts/SamplerTest.java
index 35e813e..fa30f9f 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/SamplerTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/SamplerTest.java
@@ -115,6 +115,9 @@
         assertTrue(Sampler.WRAP_LINEAR(mRS) != null);
         assertTrue(Sampler.WRAP_LINEAR_MIP_LINEAR(mRS) != null);
         assertTrue(Sampler.WRAP_NEAREST(mRS) != null);
+        assertTrue(Sampler.MIRRORED_REPEAT_NEAREST(mRS) != null);
+        assertTrue(Sampler.MIRRORED_REPEAT_LINEAR(mRS) != null);
+        assertTrue(Sampler.MIRRORED_REPEAT_LINEAR_MIP_LINEAR(mRS) != null);
     }
 
     public void testSamplerValue() {
@@ -124,9 +127,10 @@
         assertEquals(Value.LINEAR_MIP_NEAREST, Value.valueOf("LINEAR_MIP_NEAREST"));
         assertEquals(Value.WRAP, Value.valueOf("WRAP"));
         assertEquals(Value.CLAMP, Value.valueOf("CLAMP"));
+        assertEquals(Value.MIRRORED_REPEAT, Value.valueOf("MIRRORED_REPEAT"));
 
         // Make sure no new enums are added
-        assertEquals(6, Value.values().length);
+        assertEquals(7, Value.values().length);
     }
 }
 
diff --git a/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java b/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java
index f6f133a..d774fec 100644
--- a/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/GeolocationTest.java
@@ -64,6 +64,7 @@
 
     private static final String JS_INTERFACE_NAME = "Android";
     private static final int POLLING_TIMEOUT = 2000;
+    private static final String PROVIDER_NAME = "WebKitGeolocationTestLocationProvider";
 
     // static HTML page always injected instead of the url loaded
     private static final String RAW_HTML =
@@ -150,14 +151,14 @@
         mLocationManager = (LocationManager)getActivity().getApplicationContext()
                 .getSystemService(Context.LOCATION_SERVICE);
         // Add a test provider before each test to inject a location
-        addTestProvider(LocationManager.NETWORK_PROVIDER);
+        addTestProvider(PROVIDER_NAME);
     }
 
     @Override
     protected void tearDown() throws Exception {
         // Remove the test provider after each test
         try {
-          mLocationManager.removeTestProvider(LocationManager.NETWORK_PROVIDER);
+          mLocationManager.removeTestProvider(PROVIDER_NAME);
         } catch (IllegalArgumentException e) {} // Not much to do about this
         mOnUiThread.cleanUp();
         // This will null all member and static variables
@@ -179,7 +180,7 @@
     // using a maximum age.
     private void loadUrlAndUpdateLocation(String url) {
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        updateLocation(LocationManager.NETWORK_PROVIDER);
+        updateLocation(PROVIDER_NAME);
     }
 
     // WebChromeClient that accepts each location for one load. WebChromeClient is used in
diff --git a/tests/uiautomator/Android.mk b/tests/uiautomator/Android.mk
index 68b1dc2..d0d4b8e 100644
--- a/tests/uiautomator/Android.mk
+++ b/tests/uiautomator/Android.mk
@@ -22,7 +22,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_MODULE := CtsUiAutomatorTests
-LOCAL_JAVA_LIBRARIES := uiautomator_sdk_v17
+LOCAL_JAVA_LIBRARIES := uiautomator.core
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_CTS_TEST_APK := CtsUiAutomatorApp
 LOCAL_CTS_TEST_APP_PACKAGE := com.android.cts.uiautomator
diff --git a/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java b/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
index 73bab27..705c777 100644
--- a/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
+++ b/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.cts.uiautomatortest;
 
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -54,6 +55,7 @@
     protected void setUp() throws Exception {
         super.setUp();
         // Make sure the test app is always running
+        UiDevice.getInstance().waitForIdle();
         if (!new UiObject(new UiSelector().packageName(PKG_NAME)).exists())
             runShellCommand(LAUNCH_APP);
     }
@@ -195,14 +197,17 @@
     }
 
     /**
-     * Test if a the content changed due to an action can be verified
+     * Test when a node's state is changed due to an action, it is updated in the accessibility
+     * hierarchy.
      *
      * @throws UiObjectNotFoundException
      */
     public void testSelectAfterContentChanged() throws UiObjectNotFoundException {
         openTest("Test 2");
-        getObjectByText("Before").click();
-        getObjectByText("After").click();
+        UiObject dynaButton = getObjectByText("Before");
+        dynaButton.click();
+        assertTrue("Button state change is not refreshed in accessibility hierarchy",
+                getObjectByText("After").exists());
     }
 
     /**
@@ -288,12 +293,12 @@
         int totalDelay = Integer.parseInt(timeDiff);
 
         // Cumulative waits in this test should add up to at minimum 30 seconds
-        assertFalse("Timeout for wait-for-idle is too short. Expecting minimum 10 seconds",
+        assertFalse("Timeout for wait-for-idle is too short. Expecting minimum 30 seconds",
                 totalDelay < 30 * 1000);
 
         // allow for tolerance in time measurements due to differences between
         // device speeds
-        assertFalse("Timeout for wait-for-idle is too long. Expecting maximum 15 seconds",
+        assertFalse("Timeout for wait-for-idle is too long. Expecting maximum 60 seconds",
                 totalDelay > 60 * 1000);
     }
 
@@ -324,27 +329,28 @@
         UiObject textView = new UiObject(new UiSelector().textContains("["));
 
         textView.swipeLeft(10);
-        assertTrue("UiObject swipe left", "[ 2 ]".equals(textView.getText()));
+        assertTrue("UiObject swipe left 1->2", "[ 2 ]".equals(textView.getText()));
 
         textView.swipeLeft(10);
-        assertTrue("UiObject swipe left", "[ 3 ]".equals(textView.getText()));
+        assertTrue("UiObject swipe left 2->3", "[ 3 ]".equals(textView.getText()));
 
         textView.swipeLeft(10);
-        assertTrue("UiObject swipe left", "[ 4 ]".equals(textView.getText()));
+        assertTrue("UiObject swipe left 3->4", "[ 4 ]".equals(textView.getText()));
 
         textView.swipeRight(10);
-        assertTrue("UiObject swipe right", "[ 3 ]".equals(textView.getText()));
+        assertTrue("UiObject swipe right 3<-4", "[ 3 ]".equals(textView.getText()));
 
         textView.swipeRight(10);
-        assertTrue("UiObject swipe right", "[ 2 ]".equals(textView.getText()));
+        assertTrue("UiObject swipe right 2<-3", "[ 2 ]".equals(textView.getText()));
 
         textView.swipeRight(10);
-        assertTrue("UiObject swipe right", "[ 1 ]".equals(textView.getText()));
+        assertTrue("UiObject swipe right 1<-2", "[ 1 ]".equals(textView.getText()));
 
         Rect tb = textView.getBounds();
         UiDevice.getInstance().swipe(tb.right - 20, tb.centerY(), tb.left + 20, tb.centerY(), 50);
+
         SystemClock.sleep(100);
-        assertTrue("UiDevice swipe", "[ 2 ]".equals(textView.getText()));
+        assertTrue("UiDevice raw swipe 1->2", "[ 2 ]".equals(textView.getText()));
     }
 
     /**
@@ -371,45 +377,10 @@
     }
 
     /**
-     * The view contains a WebView with static content. This test uses the text
-     * traversal feature of pressing down arrows to read the view's contents
-     *
+     * Test when an object does not exist, an exception is thrown
      * @throws UiObjectNotFoundException
      */
-    /*// broken in MR1
-    public void testWebViewTextTraversal() throws UiObjectNotFoundException {
-        openTest("Test 6");
-        UiObject webView = new UiObject(new UiSelector().className(android.webkit.WebView.class
-                .getName()));
-        webView.clickTopLeft();
-        UiDevice device = UiDevice.getInstance();
-        device.clearLastTraversedText();
-
-        device.pressDPadDown();
-        String text = device.getLastTraversedText();
-        assertTrue("Read regular text", text.contains("This is test <b>6</b>"));
-
-        device.pressDPadDown();
-        text = device.getLastTraversedText();
-        assertTrue("Anchor text", text.contains("<a"));
-
-        device.pressDPadDown();
-        text = device.getLastTraversedText();
-        assertTrue("h5 text", text.contains("h5"));
-
-        device.pressDPadDown();
-        text = device.getLastTraversedText();
-        assertTrue("Anchor text", text.contains("<a"));
-
-        device.pressDPadDown();
-        text = device.getLastTraversedText();
-        assertTrue("h4 text", text.contains("h4"));
-    }*/
-
-    /**
-     * Test when an object does not exist, an exception is thrown
-     */
-    public void testExceptionObjectNotFound() {
+    public void testExceptionObjectNotFound() throws UiObjectNotFoundException {
         UiSelector selector = new UiSelector().text("Nothing should be found");
         UiSelector child = new UiSelector().className("Nothing");
         UiObject obj = new UiObject(selector.childSelector(child));
@@ -761,6 +732,7 @@
      * @since API Level 17
      */
     public void testSelectorLongClickableProperty() throws UiObjectNotFoundException {
+        openTest("Test 2");
         UiObject button3 = new UiObject(new UiSelector().className(
                 android.widget.Button.class).longClickable(true).instance(2));
         button3.longClick();
@@ -782,6 +754,234 @@
     }
 
     /**
+     * Verifies the 'Resource-Id' property of UiSelector
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public void testSelectorResourceId() throws UiObjectNotFoundException {
+        openTest("Test 5");
+        UiSelector toggleSelector =
+                new UiSelector().resourceId("com.android.cts.uiautomator:id/test_5_toggleButton");
+        UiObject toggleButton = new UiObject(toggleSelector);
+        assertTrue("Object with selector resource-id not found", toggleButton.exists());
+        assertTrue("Incorrect object for selector resource-id returned",
+                "OFF".equals(toggleButton.getText()) || "ON".equals(toggleButton.getText()));
+    }
+
+    /**
+     * Performs a pinch out from the center of a view to its edges and listens to
+     * the motion events to make sure the starting and ending points of both pointers
+     * are correct.
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public void testPinchOut() throws UiObjectNotFoundException {
+        openTest("Test 12");
+
+        UiObject screen = new UiObject(
+                new UiSelector().description("Details View"));
+
+        // get the current view dimensions
+        Rect screenRect = screen.getBounds();
+
+        // perform the pinch for 100% of the view dimensions starting form
+        // the center out to the edges.
+        screen.pinchOut(100, 30);
+
+        // dialog with the detected pointers motion coordinates is displayed.
+        UiObject results = new UiObject(new UiSelector().className(
+                android.widget.ScrollView.class).childSelector(new UiSelector().className(
+                        android.widget.TextView.class)));
+        String allPointers = results.getText();
+        new UiObject(new UiSelector().text("OK")).click(); // dismiss dialog
+
+        // parse pointer 1
+        Point p1s = parsePointerCoordinates(allPointers, 0, 0); // start
+        Point p1e = parsePointerCoordinates(allPointers, 0, 1); // end
+        // parse pointer 2
+        Point p2s = parsePointerCoordinates(allPointers, 1, 0); // start
+        Point p2e = parsePointerCoordinates(allPointers, 1, 1); // end
+
+        assertTrue("All Y axis coordinates for pointer 1 must be the same", p1s.y == p1e.y);
+        assertTrue("All Y axis coordinates for pointer 2 must be the same", p2s.y == p2e.y);
+        assertTrue("All Y axis coordinates for both pointers must be the same", p1s.y == p2s.y);
+        assertTrue("Pinch must be in center of target view", p2s.y == screenRect.centerY());
+
+        assertTrue("Touch-down X coordinate for pointer 1 is invalid",
+                withinMarginOfError(0.05f, screenRect.centerX(), p1s.x));
+
+        assertTrue("Touch-down X coordinate for pointer 2 is invalid",
+                withinMarginOfError(0.05f, screenRect.centerX(), p2s.x));
+
+        assertTrue("Touch-up X coordinate for pointer 1 is invalid",
+                withinMarginOfError(0.05f, screenRect.left, p1e.x));
+
+        assertTrue("Touch-up X coordinate for pointer 2 is invalid",
+                withinMarginOfError(0.05f, screenRect.right, p2e.x));
+    }
+
+    /**
+     * Performs a pinch in from the edges of a view to its center and listens to
+     * the motion events to make sure the starting and ending points of both pointers
+     * are correct.
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public void testPinchIn() throws UiObjectNotFoundException {
+        openTest("Test 12");
+
+        UiObject screen = new UiObject(
+                new UiSelector().description("Details View"));
+
+        // get the current view dimensions
+        Rect screenRect = screen.getBounds();
+
+        // perform the pinch for 100% of the view dimensions starting form
+        // the edges in towards the center.
+        screen.pinchIn(100, 30);
+
+        // dialog with the detected pointers motion coordinates is displayed.
+        UiObject results = new UiObject(new UiSelector().className(
+                android.widget.ScrollView.class).childSelector(new UiSelector().className(
+                        android.widget.TextView.class)));
+        String allPointers = results.getText();
+        new UiObject(new UiSelector().text("OK")).click(); // dismiss dialog
+
+        // parse pointer 1
+        Point p1s = parsePointerCoordinates(allPointers, 0, 0); // start
+        Point p1e = parsePointerCoordinates(allPointers, 0, 1); // end
+        // parse pointer 2
+        Point p2s = parsePointerCoordinates(allPointers, 1, 0); // start
+        Point p2e = parsePointerCoordinates(allPointers, 1, 1); // end
+
+        assertTrue("All Y axis coordinates for pointer 1 must be the same", p1s.y == p1e.y);
+        assertTrue("All Y axis coordinates for pointer 2 must be the same", p2s.y == p2e.y);
+        assertTrue("All Y axis coordinates for both pointers must be the same", p1s.y == p2s.y);
+        assertTrue("Pinch must be in center of target view", p2s.y == screenRect.centerY());
+
+        assertTrue("Touch-down X coordinate for pointer 1 is invalid",
+                withinMarginOfError(0.05f, screenRect.left, p1s.x));
+
+        assertTrue("Touch-down X coordinate for pointer 2 is invalid",
+                withinMarginOfError(0.05f, screenRect.right, p2s.x));
+
+        assertTrue("Touch-up X coordinate for pointer 1 is invalid",
+                withinMarginOfError(0.05f, screenRect.centerX(), p1e.x));
+
+        assertTrue("Touch-up X coordinate for pointer 2 is invalid",
+                withinMarginOfError(0.05f, screenRect.centerX(), p2e.x));
+    }
+
+    /**
+     * Performs a drag and drop operation from one UiObject to another UiObject
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+    public void testDragToObject() throws UiObjectNotFoundException {
+        openTest("Test 5");
+
+        UiObject imageButton = new UiObject(new UiSelector().description("Image button"));
+        UiObject starsBar = new UiObject(new UiSelector().className(android.widget.RatingBar.class));
+
+        Rect starsBarRect = starsBar.getBounds();
+        Rect imageButtonRect = imageButton.getBounds();
+        imageButton.dragTo(starsBar, 30);
+
+        // dialog with the detected pointers motion coordinates is displayed.
+        UiObject results = new UiObject(new UiSelector().className(
+                android.widget.ScrollView.class).childSelector(new UiSelector().className(
+                        android.widget.TextView.class)));
+        String allPointers = results.getText();
+        new UiObject(new UiSelector().text("OK")).click(); // dismiss dialog
+
+        // parse pointer 1
+        Point p1s = parsePointerCoordinates(allPointers, 0, 0); // start
+        Point p1e = parsePointerCoordinates(allPointers, 0, 1); // end
+
+        assertTrue("Invalid touch starting.X reported",
+                withinMarginOfError(0.05f, imageButtonRect.centerX(), p1s.x));
+        assertTrue("Invalid touch starting.Y reported",
+                withinMarginOfError(0.05f, imageButtonRect.centerY(), p1s.y));
+        assertTrue("Invalid touch ending.X reported",
+                withinMarginOfError(0.05f, starsBarRect.centerX(), p1e.x));
+        assertTrue("Invalid touch ending.Y reported",
+                withinMarginOfError(0.05f, starsBarRect.centerY(), p1e.y));
+    }
+
+    /**
+     * Performs a drag and drop operation from one UiObject to a specified coordinates
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 18
+     */
+   public void testDragToCoordinates() throws UiObjectNotFoundException {
+       openTest("Test 5");
+
+       UiObject imageButton = new UiObject(new UiSelector().description("Image button"));
+       UiObject starsBar = new UiObject(new UiSelector().className(android.widget.RatingBar.class));
+
+       Rect starsBarRect = starsBar.getBounds();
+       Rect imageButtonRect = imageButton.getBounds();
+       imageButton.dragTo(starsBarRect.centerX(), starsBarRect.centerY(), 30);
+
+       // dialog with the detected pointers motion coordinates is displayed.
+       UiObject results = new UiObject(new UiSelector().className(
+               android.widget.ScrollView.class).childSelector(new UiSelector().className(
+                       android.widget.TextView.class)));
+       String allPointers = results.getText();
+       new UiObject(new UiSelector().text("OK")).click(); // dismiss dialog
+
+       // parse pointer 1
+       Point p1s = parsePointerCoordinates(allPointers, 0, 0); // start
+       Point p1e = parsePointerCoordinates(allPointers, 0, 1); // end
+
+       assertTrue("Invalid touch starting.X reported",
+               withinMarginOfError(0.05f, imageButtonRect.centerX(), p1s.x));
+       assertTrue("Invalid touch starting.Y reported",
+               withinMarginOfError(0.05f, imageButtonRect.centerY(), p1s.y));
+       assertTrue("Invalid touch ending.X reported",
+               withinMarginOfError(0.05f, starsBarRect.centerX(), p1e.x));
+       assertTrue("Invalid touch ending.Y reported",
+               withinMarginOfError(0.05f, starsBarRect.centerY(), p1e.y));
+   }
+
+   /**
+    * Detect if actual value is within the allowable margin of error of the expected value.
+    *
+    * Used essentially with actual values that may vary from the expected values such in the
+    * cases of touch and pinch and touch and swipe where the starting or ending points may
+    * not exactly match the expected value.
+    *
+    * @param marginPrecent is values between 0 and 1
+    * @param expected
+    * @param actual
+    * @return true if actual is within the allowed range from expected
+    */
+   private boolean withinMarginOfError(float marginPrecent, int expected, int actual) {
+       int m = (int) (marginPrecent * expected);
+       return actual >= expected - m && actual <= expected + m;
+   }
+
+   /**
+     * Parses a string containing starting to ending coordinates of one or more pointers.
+     *
+     * @param allPointers is a raw string with coordinates from all detected pointers
+     * @param pointerNumber is the desired pointer to be parsed
+     * @param edge is the 0 for the start or 1 for the end of the swipe
+     * @return Point containing the start or end coordinates of the specified pointer number
+     */
+    private Point parsePointerCoordinates(String allPointers, int pointerNumber, int edge) {
+        String pointers[] = allPointers.split("\n");
+        String coordinates = pointers[pointerNumber].split(":")[edge];
+        String xy[] = coordinates.split(",");
+        return new Point(Integer.parseInt(xy[0]), Integer.parseInt(xy[1]));
+    }
+
+    /**
      * Private helper to open test views. Also covers UiScrollable tests
      *
      * @param name
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/test_results_detail_fragment.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/test_results_detail_fragment.xml
index 28ed6dd..e8b8c05 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/test_results_detail_fragment.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/layout/test_results_detail_fragment.xml
@@ -19,6 +19,7 @@
     android:id="@+id/test_results_detail_container"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:contentDescription="@string/title_test_view_detail"
     android:orientation="vertical" >
 
     <TextView
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml
index db2450a..04f0df4 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml
@@ -57,5 +57,7 @@
     <string name="title_activity_test5_detail">Test5DetailActivity</string>
     <string name="title_activity_test6_detail">Test6DetailActivity</string>
     <string name="test_5_Widgets_collection">Widgets Collection</string>
-
+    <string name="generic_item_touch_dialog_title">Multi-touch test dialog</string>
+    <string name="drag_item_touch_dialog_title">Drag and drop test dialog</string>
+    <string name="title_test_view_detail">Details View</string>
 </resources>
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java
index 4eade4b..4fb322f 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java
@@ -30,7 +30,6 @@
 public class Test2DetailFragment extends Fragment {
     public static final String ARG_ITEM_ID = "item_id";
     private Button mButton1, mButton2, mButton3, mDynaButton;
-    private boolean mDynaButtonAfter = false;
 
     public Test2DetailFragment() {
     }
@@ -144,32 +143,11 @@
         mDynaButton.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View v) {
-                if (getActivity().getString(R.string.buttonBefore).equals(mDynaButton.getText())) {
-                    mDynaButton.setText(R.string.buttonAfter);
-                    mDynaButton
-                            .setContentDescription(getActivity().getString(R.string.buttonAfter));
-                    mDynaButtonAfter = true;
-                } else {
-                    mDynaButton.setText(R.string.buttonBefore);
-                    mDynaButton.setContentDescription(getActivity()
-                            .getString(R.string.buttonBefore));
-                    mDynaButtonAfter = false;
-                }
+                mDynaButton.setText(R.string.buttonAfter);
+                mDynaButton.setContentDescription(getString(R.string.buttonAfter));
             }
         });
 
-        if (savedState != null && savedState.getBoolean("DynaButtonAfter")) {
-            mDynaButton.setText(R.string.buttonAfter);
-            mDynaButton.setContentDescription(getActivity().getString(R.string.buttonAfter));
-            mDynaButtonAfter = true;
-        }
         return rootView;
     }
-
-    @Override
-    public void onSaveInstanceState(Bundle savedInstanceState) {
-        super.onSaveInstanceState(savedInstanceState);
-        // Save UI state changes to the savedInstanceState.
-        savedInstanceState.putBoolean("DynaButtonAfter", mDynaButtonAfter);
-    }
 }
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test5DetailFragment.java b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test5DetailFragment.java
index 0f88d3c..e2dd156 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test5DetailFragment.java
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test5DetailFragment.java
@@ -16,9 +16,11 @@
 
 package com.android.cts.uiautomator;
 
+import android.app.AlertDialog;
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
@@ -31,6 +33,15 @@
 
     public static final String ARG_ITEM_ID = "item_id";
 
+    class PointerEvent {
+        int startX;
+        int startY;
+        int endX;
+        int endY;
+    }
+
+    private final PointerEvent mPointerEvent = new PointerEvent();
+
     public Test5DetailFragment() {
     }
 
@@ -66,6 +77,75 @@
             }
         });
 
+        imageButton.setOnTouchListener(new ImageButton.OnTouchListener() {
+
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                if (event.getAction() == MotionEvent.ACTION_DOWN) {
+                    resetTouchResults();
+                    collectStartAction(event, v);
+                } else if (event.getAction() == MotionEvent.ACTION_UP) {
+                    collectEndAction(event, v);
+                    displayTouchResults();
+                }
+                return false;
+            }
+        });
+
         return rootView;
     }
+
+    private void displayTouchResults() {
+        StringBuilder output = new StringBuilder();
+
+        output.append(String.format("%d,%d:%d,%d\n",
+                mPointerEvent.startX, mPointerEvent.startY, mPointerEvent.endX,
+                mPointerEvent.endY));
+
+        // display the submitted text
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setTitle(R.string.drag_item_touch_dialog_title);
+        builder.setPositiveButton(R.string.OK, null);
+        builder.setMessage(output.toString());
+        AlertDialog diag = builder.create();
+        diag.show();
+    }
+
+    /**
+     * Clears all collected pointer results
+     */
+    private void resetTouchResults() {
+         mPointerEvent.startX = mPointerEvent.startY =
+                    mPointerEvent.endX = mPointerEvent.endY = -1;
+    }
+
+    /**
+     * Collects pointer touch information converting from relative to absolute before
+     * storing it as starting touch coordinates.
+     *
+     * @param event
+     * @param view
+     * @param pointerIndex
+     */
+    private void collectStartAction(MotionEvent event, View view) {
+        int offsetInScreen[] = new int[2];
+        view.getLocationOnScreen(offsetInScreen);
+        mPointerEvent.startX = (int)(event.getX() + offsetInScreen[0]);
+        mPointerEvent.startY = (int)(event.getY() + offsetInScreen[1]);
+    }
+
+    /**
+     * Collects pointer touch information converting from relative to absolute before
+     * storing it as ending touch coordinates.
+     *
+     * @param event
+     * @param view
+     * @param pointerIndex
+     */
+    private void collectEndAction(MotionEvent event, View view) {
+        int offsetInScreen[] = new int[2];
+        view.getLocationOnScreen(offsetInScreen);
+        mPointerEvent.endX = (int)(event.getX() + offsetInScreen[0]);
+        mPointerEvent.endY = (int)(event.getY() + offsetInScreen[1]);
+    }
 }
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/TestGenericDetailFragment.java b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/TestGenericDetailFragment.java
index ab36d04..a7215c3 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/TestGenericDetailFragment.java
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/TestGenericDetailFragment.java
@@ -16,9 +16,11 @@
 
 package com.android.cts.uiautomator;
 
+import android.app.AlertDialog;
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
@@ -27,6 +29,15 @@
     public static final String ARG_ITEM_ID = "item_id";
     TestItems.TestItem mItem;
 
+    private class PointerEvent {
+        int startX;
+        int startY;
+        int endX;
+        int endY;
+    }
+
+    private final PointerEvent[] mPointerEvents = new PointerEvent[10];
+
     public TestGenericDetailFragment() {
     }
 
@@ -44,6 +55,122 @@
         if (mItem != null) {
             ((TextView) rootView.findViewById(R.id.testResultsTextView)).setText(mItem.mName);
         }
+
+        // listen to touch events to verify the multiPointerGesture APIs
+        // Since API Level 18
+        rootView.setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+
+                switch(event.getAction() & MotionEvent.ACTION_MASK) {
+                    case MotionEvent.ACTION_DOWN:
+                        // Reset any collected touch coordinate results on the primary touch down
+                        resetTouchResults();
+                        // collect this event
+                        collectStartAction(event, v, 0);
+                        break;
+
+                    case MotionEvent.ACTION_POINTER_DOWN:
+                        // collect this event
+                        collectStartAction(event, v, getPointerIndex(event));
+                        break;
+
+                    case MotionEvent.ACTION_POINTER_UP:
+                        // collect this event
+                        collectEndAction(event, v, getPointerIndex(event));
+                        break;
+
+                    case MotionEvent.ACTION_UP:
+                        // collect this event
+                        collectEndAction(event, v, 0);
+                        // on the primary touch up display results collected for all pointers
+                        displayTouchResults();
+                        break;
+                }
+                return true;
+            }
+        });
+
         return rootView;
     }
+
+    /**
+     * Displays collected results from all pointers into a dialog view in the following
+     * format: "startX,startY:endX,endY" where each line represent data for a pointer if
+     * multiple pointers (fingers) were detected.
+     */
+    private void displayTouchResults() {
+        StringBuilder output = new StringBuilder();
+        for (int x = 0; x < mPointerEvents.length; x++) {
+            if (mPointerEvents[x].startX == -1)
+                break;
+
+            output.append(String.format("%d,%d:%d,%d\n",
+                    mPointerEvents[x].startX, mPointerEvents[x].startY, mPointerEvents[x].endX,
+                    mPointerEvents[x].endY));
+        }
+
+        // display the submitted text
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setTitle(R.string.generic_item_touch_dialog_title);
+        builder.setPositiveButton(R.string.OK, null);
+        builder.setMessage(output.toString());
+        AlertDialog diag = builder.create();
+        diag.show();
+    }
+
+    /**
+     * Clears all collected pointer results
+     */
+    private void resetTouchResults() {
+        for (int x = 0; x < mPointerEvents.length; x++) {
+            if (mPointerEvents[x] == null)
+                mPointerEvents[x] = new PointerEvent();
+            mPointerEvents[x].startX = mPointerEvents[x].startY =
+                    mPointerEvents[x].endX = mPointerEvents[x].endY = -1;
+        }
+    }
+
+    /**
+     * Collects pointer touch information converting from relative to absolute before
+     * storing it as starting touch coordinates.
+     *
+     * @param event
+     * @param view
+     * @param pointerIndex
+     */
+    private void collectStartAction(MotionEvent event, View view, int pointerIndex) {
+        int offsetInScreen[] = new int[2];
+        view.getLocationOnScreen(offsetInScreen);
+        mPointerEvents[getPointerId(event)].startX =
+                (int)(event.getX(pointerIndex) + offsetInScreen[0]);
+        mPointerEvents[getPointerId(event)].startY =
+                (int)(event.getY(pointerIndex) + offsetInScreen[1]);
+    }
+
+    /**
+     * Collects pointer touch information converting from relative to absolute before
+     * storing it as ending touch coordinates.
+     *
+     * @param event
+     * @param view
+     * @param pointerIndex
+     */
+    private void collectEndAction(MotionEvent event, View view, int pointerIndex) {
+        int offsetInScreen[] = new int[2];
+        view.getLocationOnScreen(offsetInScreen);
+        mPointerEvents[getPointerId(event)].endX =
+                (int)(event.getX(pointerIndex) + offsetInScreen[0]);
+        mPointerEvents[getPointerId(event)].endY =
+                (int)(event.getY(pointerIndex) + offsetInScreen[1]);
+    }
+
+    private int getPointerId(MotionEvent event) {
+        return event.getPointerId(getPointerIndex(event));
+    }
+
+    private int getPointerIndex(MotionEvent event) {
+        return ((event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
+                >> MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+    }
 }