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);
+ }
}