Merge "Add --class and --method options to cts tradefed." into honeycomb
diff --git a/apps/CtsVerifier/arduino-helper/arduino-helper.pde b/apps/CtsVerifier/arduino-helper/arduino-helper.pde
index 0c41388..7d56096 100644
--- a/apps/CtsVerifier/arduino-helper/arduino-helper.pde
+++ b/apps/CtsVerifier/arduino-helper/arduino-helper.pde
@@ -23,7 +23,7 @@
* sender in case data is lost and the fixed-byte messages
* get out of sync.
*/
-#define MESSAGE_SIZE 128
+#define MESSAGE_SIZE 128 // serial buffer size on TTL is 128 bytes
#define MESSAGE_DELIMITER 0xBEEF
#define MESSAGE_ESCAPE 0x2a
struct message {
@@ -62,23 +62,23 @@
uint16_t id;
uint16_t playfield_width;
uint16_t playfield_height;
- uint16_t paddle_width;
- uint16_t paddle_offset;
- uint16_t max_paddle_motion;
- uint8_t unused[MESSAGE_SIZE - 14];
+ uint16_t ball_x;
+ uint16_t ball_y;
+ uint16_t ball_radius;
+ uint16_t ball_velocity_x;
+ uint16_t ball_velocity_y;
+ uint8_t unused[MESSAGE_SIZE - 18];
};
#define OPCODE_PONG_BALL_STATE (OPCODE_RESET + 4)
struct pong_ball_state {
uint16_t opcode;
uint16_t id;
- uint16_t ball_x;
- uint16_t ball_y;
- uint8_t unused[MESSAGE_SIZE - 8];
+ uint8_t unused[MESSAGE_SIZE - 4];
};
struct wall_time_struct {
- uint32_t raw; // long == 4-byte on AVR
+ uint32_t raw;
uint8_t initialized;
uint8_t hours;
uint8_t minutes;
@@ -86,20 +86,47 @@
};
struct wall_time_struct WALL_TIME;
+/*
+ * Main ball-playing state record.
+ */
struct pong_state_struct {
uint16_t playfield_width;
uint16_t playfield_height;
- uint16_t paddle_width;
- uint16_t paddle_offset;
- uint16_t max_paddle_motion;
- uint16_t paddle_x;
- uint16_t last_ball_x;
- uint16_t last_ball_y;
+ int16_t ball_x;
+ int16_t ball_y;
+ int16_t ball_radius;
+ int16_t velocity_x;
+ int16_t velocity_y;
};
struct pong_state_struct PONG_STATE;
-void print_current_time() {
+/* This is a temporary buffer used by the message handler */
+struct message_buffer {
+ uint16_t count; // number of bytes read into the buffer
+ uint8_t buffer[MESSAGE_SIZE]; // contents of a 'struct message'
+};
+struct message_buffer MESSAGE_BUFFER;
+
+
+/*
+ * Clears all stateful values, including the wall clock time, current message
+ * data, and user/app state. Also clears the message handler's buffer. By
+ * "clear" we mean "memset to 0".
+ */
+void reset() {
+ memset(&WALL_TIME, 0, sizeof(WALL_TIME));
+ memset(&CURRENT_MESSAGE, 0, sizeof(CURRENT_MESSAGE));
+ memset(&MESSAGE_BUFFER, 0, sizeof(MESSAGE_BUFFER));
+ memset(&PONG_STATE, 0, sizeof(PONG_STATE));
+}
+
+
+/*
+ * Closes out a serial response, which involves sending a blank line in
+ * between messages. Also prints the current time, for funsies.
+ */
+void close_response() {
if (WALL_TIME.initialized) {
Serial.print("current_time=");
Serial.print(WALL_TIME.hours, DEC);
@@ -114,100 +141,126 @@
} else {
Serial.println("current_time=00:00:00");
}
+ Serial.print("\r\n");
}
+/*
+ * Opcode processor/handler.
+ */
void handle_current_message() {
- static uint16_t last_id;
+ static uint16_t last_id = 999999;
static struct setmode_pong* setmode_pong_msg;
- static struct pong_ball_state* pong_ball_state_msg;
- static uint16_t paddle_half_width;
- static uint16_t paddle_max;
- static uint16_t danger;
- static uint8_t invert;
- static uint16_t delta;
- if (CURRENT_MESSAGE.id == 0 || CURRENT_MESSAGE.id == last_id) {
+ if (CURRENT_MESSAGE.id == last_id) {
return;
}
last_id = CURRENT_MESSAGE.id;
switch (CURRENT_MESSAGE.opcode) {
- case OPCODE_SETMODE_PONG:
+ case OPCODE_SETMODE_PONG: // initialize ball animation state
memset(&PONG_STATE, 0, sizeof(PONG_STATE));
setmode_pong_msg = (struct setmode_pong*)(&CURRENT_MESSAGE);
PONG_STATE.playfield_width = setmode_pong_msg->playfield_width;
PONG_STATE.playfield_height = setmode_pong_msg->playfield_height;
- PONG_STATE.paddle_width = setmode_pong_msg->paddle_width;
- PONG_STATE.paddle_offset = setmode_pong_msg->paddle_offset;
- PONG_STATE.max_paddle_motion = setmode_pong_msg->max_paddle_motion;
-
- paddle_half_width = PONG_STATE.paddle_width / 2;
- paddle_max = PONG_STATE.playfield_width - paddle_half_width;
+ PONG_STATE.ball_x = setmode_pong_msg->ball_x;
+ PONG_STATE.ball_y = setmode_pong_msg->ball_y;
+ PONG_STATE.ball_radius = setmode_pong_msg->ball_radius;
+ PONG_STATE.velocity_x = setmode_pong_msg->ball_velocity_x;
+ PONG_STATE.velocity_y = setmode_pong_msg->ball_velocity_y;
Serial.println("message_type=setmode_pong_ack");
Serial.print("id=");
Serial.println(CURRENT_MESSAGE.id);
- print_current_time();
- Serial.println("");
+ Serial.print("ball_x=");
+ Serial.println(PONG_STATE.ball_x, DEC);
+ Serial.print("ball_y=");
+ Serial.println(PONG_STATE.ball_y, DEC);
+ Serial.print("ball_velocity_x=");
+ Serial.println(PONG_STATE.velocity_x, DEC);
+ Serial.print("ball_velocity_y=");
+ Serial.println(PONG_STATE.velocity_y, DEC);
+ Serial.print("playfield_width=");
+ Serial.println(PONG_STATE.playfield_width, DEC);
+ Serial.print("playfield_height=");
+ Serial.println(PONG_STATE.playfield_height, DEC);
+ Serial.print("ball_radius=");
+ Serial.println(PONG_STATE.ball_radius, DEC);
+ close_response();
break;
- case OPCODE_PONG_BALL_STATE:
- pong_ball_state_msg = (struct pong_ball_state*)(&CURRENT_MESSAGE);
- danger = pong_ball_state_msg->ball_x - PONG_STATE.paddle_x;
- invert = (danger < 0);
- danger *= invert ? -1 : 1;
- if (danger < paddle_half_width) {
- delta = 0;
- } else if (danger < PONG_STATE.playfield_width / 3) {
- delta = PONG_STATE.max_paddle_motion / 3;
- } else if (danger < PONG_STATE.playfield_width * 2 / 3) {
- delta = PONG_STATE.max_paddle_motion * 2 / 3;
- } else {
- delta = PONG_STATE.max_paddle_motion;
+ case OPCODE_PONG_BALL_STATE: // update a frame
+ /* This gets called once per update/refresh request from host. From the
+ * perspective of the AVR, we are running this animation in arbitrary
+ * time units. If host calls this at 10 FPS, we return data at 10 FPS.
+ * If it calls us at 100FPS, we return data at 100FPS. */
+ PONG_STATE.ball_x += PONG_STATE.velocity_x;
+ PONG_STATE.ball_y += PONG_STATE.velocity_y;
+
+ // all we do is bounce around the inside of the box we were given
+ if ((PONG_STATE.ball_x - PONG_STATE.ball_radius) < 0) {
+ PONG_STATE.velocity_x *= -1;
+ PONG_STATE.ball_x = PONG_STATE.ball_radius;
+ } else if ((PONG_STATE.ball_x + PONG_STATE.ball_radius) > PONG_STATE.playfield_width) {
+ PONG_STATE.velocity_x *= -1;
+ PONG_STATE.ball_x = PONG_STATE.playfield_width - PONG_STATE.ball_radius;
}
- delta *= invert ? 1 : -1;
- PONG_STATE.paddle_x += delta;
- if (PONG_STATE.paddle_x < paddle_half_width) {
- PONG_STATE.paddle_x = paddle_half_width;
- } else if (PONG_STATE.paddle_x > paddle_max) {
- PONG_STATE.paddle_x = paddle_max;
+
+ if ((PONG_STATE.ball_y - PONG_STATE.ball_radius) < 0) {
+ PONG_STATE.velocity_y *= -1;
+ PONG_STATE.ball_y = PONG_STATE.ball_radius;
+ } else if ((PONG_STATE.ball_y + PONG_STATE.ball_radius) > PONG_STATE.playfield_height) {
+ PONG_STATE.velocity_y *= -1;
+ PONG_STATE.ball_y = PONG_STATE.playfield_height - PONG_STATE.ball_radius;
}
Serial.println("message_type=pong_paddle_state");
Serial.print("id=");
+ Serial.println(CURRENT_MESSAGE.id, DEC);
+ Serial.print("ball_x=");
+ Serial.println(PONG_STATE.ball_x, DEC);
+ Serial.print("ball_y=");
+ Serial.println(PONG_STATE.ball_y, DEC);
+ close_response();
+ break;
+
+ case OPCODE_RESET:
+ reset();
+ Serial.println("message_type=reset_ack");
+ Serial.print("id=");
Serial.println(CURRENT_MESSAGE.id);
- print_current_time();
- Serial.print("paddle_x=");
- Serial.println(PONG_STATE.paddle_x);
- Serial.println("");
+ close_response();
+ break;
+
+ case OPCODE_INIT_TIME:
+ // cast CURRENT_MESSAGE to our time struct to conveniently fetch
+ // out the current time
+ WALL_TIME.raw = ((struct init_time*)(&CURRENT_MESSAGE))->cur_raw_time;
+ WALL_TIME.initialized = 1;
+
+ Serial.println("message_type=init_time_ack");
+ Serial.print("id=");
+ Serial.println(CURRENT_MESSAGE.id);
+ close_response();
+ break;
+
+ case OPCODE_CURRENT_TIME:
+ Serial.println("message_type=current_time_ack");
+ Serial.print("id=");
+ Serial.println(CURRENT_MESSAGE.id);
+ close_response();
break;
default:
+ Serial.println("message_type=unknown_command_ack");
+ Serial.print("id=");
+ Serial.println(CURRENT_MESSAGE.id);
+ close_response();
break;
}
}
-/* This is a temporary buffer used by the message handler */
-struct message_buffer {
- uint16_t count; // number of bytes read into the buffer
- uint8_t buffer[MESSAGE_SIZE]; // contents of a 'struct message'
-};
-struct message_buffer MESSAGE_BUFFER;
-
-/*
- * Clears all stateful values, including the wall clock time, current message
- * data, and user/app state. Also clears the message handler's buffer. By
- * "clear" we mean "memset to 0".
- */
-void reset() {
- memset(&WALL_TIME, 0, sizeof(WALL_TIME));
- memset(&CURRENT_MESSAGE, 0, sizeof(CURRENT_MESSAGE));
- memset(&MESSAGE_BUFFER, 0, sizeof(MESSAGE_BUFFER));
- memset(&PONG_STATE, 0, sizeof(PONG_STATE));
-}
-
/*
* Pumps the message processor. That is, this function is intended to be
@@ -226,16 +279,10 @@
static uint8_t cur_byte;
static uint16_t* cur_word;
static int8_t delimiter_index;
- static char buf[4];
+ static char buf[6];
while (Serial.available() > 0) { // keep going as long as we might have messages
cur_byte = (uint8_t)(Serial.read() & 0x000000ff);
MESSAGE_BUFFER.buffer[(MESSAGE_BUFFER.count)++] = cur_byte;
- Serial.print("booga ");
- Serial.print(itoa(MESSAGE_BUFFER.count, buf, 10));
- Serial.print(" ");
- Serial.print(itoa(Serial.available(), buf, 10));
- Serial.print(" ");
- Serial.println(itoa(cur_byte, buf, 10));
if (MESSAGE_BUFFER.count >= MESSAGE_SIZE) {
if ((*(uint16_t*)(MESSAGE_BUFFER.buffer + MESSAGE_SIZE - 2)) != MESSAGE_DELIMITER) {
// whoops, we got out of sync with the transmitter. Scan current
@@ -251,10 +298,6 @@
}
}
}
- Serial.print("klaxon ");
- Serial.println(itoa(delimiter_index, buf, 10));
- Serial.print("klaxon ");
- Serial.println(itoa(*((uint16_t*)(MESSAGE_BUFFER.buffer + MESSAGE_SIZE - 2)), buf, 10));
MESSAGE_BUFFER.count = 0;
if (delimiter_index >= 0) {
for (int i = delimiter_index + 2; i < MESSAGE_SIZE; ++i, ++(MESSAGE_BUFFER.count)) {
@@ -262,47 +305,16 @@
}
}
memset(MESSAGE_BUFFER.buffer + MESSAGE_BUFFER.count, 0, MESSAGE_SIZE - MESSAGE_BUFFER.count);
+ close_response();
} else {
memcpy(&CURRENT_MESSAGE, MESSAGE_BUFFER.buffer, MESSAGE_SIZE);
memset(&MESSAGE_BUFFER, 0, sizeof(MESSAGE_BUFFER));
- switch (CURRENT_MESSAGE.opcode) {
- case OPCODE_RESET:
- reset();
- return;
-
- case OPCODE_INIT_TIME:
- // cast CURRENT_MESSAGE to our time struct to conveniently fetch
- // out the current time
- WALL_TIME.raw = ((struct init_time*)(&CURRENT_MESSAGE))->cur_raw_time;
- WALL_TIME.initialized = 1;
-
- Serial.println("message_type=init_time_ack");
- Serial.print("id=");
- Serial.println(CURRENT_MESSAGE.id);
- print_current_time();
- Serial.println("");
-
- CURRENT_MESSAGE.id = 0;
- break;
-
- case OPCODE_CURRENT_TIME:
- Serial.println("message_type=current_time_ack");
- Serial.print("id=");
- Serial.println(CURRENT_MESSAGE.id);
- print_current_time();
- Serial.println("");
-
- CURRENT_MESSAGE.id = 0;
-
- default:
- // no-op -- actually means main loop will handle it
- break;
- }
}
}
}
}
+
/*
* Pumps the system wall clock. This checks the device's monotonic clock to
* determine elapsed time since last invocation, and updates wall clock time
@@ -320,7 +332,6 @@
if (millis() / 1000 != tmp_prev_millis) {
tmp_prev_millis = millis() / 1000;
- print_current_time();
}
if (WALL_TIME.initialized) {
@@ -348,15 +359,7 @@
* Standard Arduino loop-pump hook.
*/
void loop() {
- static uint16_t last_id = 0;
-
- // pump the clock and message processor
pump_clock();
pump_message_processor();
-
- // ignore any "system" messages (those with ID == 0) but dispatch app messages
- if ((last_id != CURRENT_MESSAGE.id) && (CURRENT_MESSAGE.id != 0)) {
- handle_current_message();
- }
- last_id = CURRENT_MESSAGE.id;
+ handle_current_message();
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
index 4f6e0a3..c83a470 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
@@ -92,8 +92,9 @@
};
/**
- * A list of all features added in FroYo (API=8). Because we want to run on
- * Eclair devices, we can't use static references to constants added later
+ * A list of all features added in FroYo (API=8) and Gingerbread (API=9).
+ * Because we want to run on Eclair devices,
+ * we can't use static references to constants added later
* than Eclair. We could use Reflection, but we'd still need a list of
* string literals (for constant names) anyway, and there's little point in
* using Reflection to to look up a constant String value for a constant
@@ -113,6 +114,17 @@
new Feature("android.hardware.wifi", false),
};
+ public static final Feature[] ALL_GINGERBREAD_FEATURES = {
+ new Feature("android.hardware.audio.low_latency", false),
+ new Feature("android.hardware.camera.front", false),
+ new Feature("android.hardware.nfc", false),
+ new Feature("android.hardware.sensor.barometer", false),
+ new Feature("android.hardware.sensor.gyroscope", false),
+ new Feature("android.hardware.touchscreen.multitouch.jazzhand", false),
+ new Feature("android.software.sip", false),
+ new Feature("android.software.sip.voip", false),
+ };
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -147,6 +159,9 @@
if (apiVersion >= Build.VERSION_CODES.FROYO) {
Collections.addAll(features, ALL_FROYO_FEATURES);
}
+ if (apiVersion >= Build.VERSION_CODES.GINGERBREAD) {
+ Collections.addAll(features, ALL_GINGERBREAD_FEATURES);
+ }
for (Feature f : features) {
HashMap<String, Object> row = new HashMap<String, Object>();
listViewData.add(row);
diff --git a/apps/CtsVerifier/tests/src/com/android/cts/verifier/features/FeatureSummaryActivityTest.java b/apps/CtsVerifier/tests/src/com/android/cts/verifier/features/FeatureSummaryActivityTest.java
index ca7ced4..d05075b 100644
--- a/apps/CtsVerifier/tests/src/com/android/cts/verifier/features/FeatureSummaryActivityTest.java
+++ b/apps/CtsVerifier/tests/src/com/android/cts/verifier/features/FeatureSummaryActivityTest.java
@@ -43,6 +43,11 @@
actualFeatures.add(feature.name);
}
}
+ if (version >= Build.VERSION_CODES.GINGERBREAD) {
+ for (Feature feature : FeatureSummaryActivity.ALL_GINGERBREAD_FEATURES) {
+ actualFeatures.add(feature.name);
+ }
+ }
assertEquals("Feature list needs to be updated.",
expectedFeatures.size(), actualFeatures.size());
diff --git a/libs/json/Android.mk b/libs/json/Android.mk
new file mode 100644
index 0000000..7ec9e79
--- /dev/null
+++ b/libs/json/Android.mk
@@ -0,0 +1,25 @@
+#
+# Copyright (C) 2010 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := jsonlib
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
diff --git a/libs/json/src/com/android/json/stream/JsonReader.java b/libs/json/src/com/android/json/stream/JsonReader.java
new file mode 100644
index 0000000..d912d62
--- /dev/null
+++ b/libs/json/src/com/android/json/stream/JsonReader.java
@@ -0,0 +1,1106 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.json.stream;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded value as a stream of tokens. This stream includes both literal
+ * values (strings, numbers, booleans, and nulls) as well as the begin and
+ * end delimiters of objects and arrays. The tokens are traversed in
+ * depth-first order, the same order that they appear in the JSON document.
+ * Within JSON objects, name/value pairs are represented by a single token.
+ *
+ * <h3>Parsing JSON</h3>
+ * To create a recursive descent parser for your own JSON streams, first create
+ * an entry point method that creates a {@code JsonReader}.
+ *
+ * <p>Next, create handler methods for each structure in your JSON text. You'll
+ * need a method for each object type and for each array type.
+ * <ul>
+ * <li>Within <strong>array handling</strong> methods, first call {@link
+ * #beginArray} to consume the array's opening bracket. Then create a
+ * while loop that accumulates values, terminating when {@link #hasNext}
+ * is false. Finally, read the array's closing bracket by calling {@link
+ * #endArray}.
+ * <li>Within <strong>object handling</strong> methods, first call {@link
+ * #beginObject} to consume the object's opening brace. Then create a
+ * while loop that assigns values to local variables based on their name.
+ * This loop should terminate when {@link #hasNext} is false. Finally,
+ * read the object's closing brace by calling {@link #endObject}.
+ * </ul>
+ * <p>When a nested object or array is encountered, delegate to the
+ * corresponding handler method.
+ *
+ * <p>When an unknown name is encountered, strict parsers should fail with an
+ * exception. Lenient parsers should call {@link #skipValue()} to recursively
+ * skip the value's nested tokens, which may otherwise conflict.
+ *
+ * <p>If a value may be null, you should first check using {@link #peek()}.
+ * Null literals can be consumed using either {@link #nextNull()} or {@link
+ * #skipValue()}.
+ *
+ * <h3>Example</h3>
+ * Suppose we'd like to parse a stream of messages such as the following: <pre> {@code
+ * [
+ * {
+ * "id": 912345678901,
+ * "text": "How do I read JSON on Android?",
+ * "geo": null,
+ * "user": {
+ * "name": "android_newb",
+ * "followers_count": 41
+ * }
+ * },
+ * {
+ * "id": 912345678902,
+ * "text": "@android_newb just use android.util.JsonReader!",
+ * "geo": [50.454722, -104.606667],
+ * "user": {
+ * "name": "jesse",
+ * "followers_count": 2
+ * }
+ * }
+ * ]}</pre>
+ * This code implements the parser for the above structure: <pre> {@code
+ *
+ * public List<Message> readJsonStream(InputStream in) throws IOException {
+ * JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
+ * return readMessagesArray(reader);
+ * }
+ *
+ * public List<Message> readMessagesArray(JsonReader reader) throws IOException {
+ * List<Message> messages = new ArrayList<Message>();
+ *
+ * reader.beginArray();
+ * while (reader.hasNext()) {
+ * messages.add(readMessage(reader));
+ * }
+ * reader.endArray();
+ * return messages;
+ * }
+ *
+ * public Message readMessage(JsonReader reader) throws IOException {
+ * long id = -1;
+ * String text = null;
+ * User user = null;
+ * List<Double> geo = null;
+ *
+ * reader.beginObject();
+ * while (reader.hasNext()) {
+ * String name = reader.nextName();
+ * if (name.equals("id")) {
+ * id = reader.nextLong();
+ * } else if (name.equals("text")) {
+ * text = reader.nextString();
+ * } else if (name.equals("geo") && reader.peek() != JsonToken.NULL) {
+ * geo = readDoublesArray(reader);
+ * } else if (name.equals("user")) {
+ * user = readUser(reader);
+ * } else {
+ * reader.skipValue();
+ * }
+ * }
+ * reader.endObject();
+ * return new Message(id, text, user, geo);
+ * }
+ *
+ * public List<Double> readDoublesArray(JsonReader reader) throws IOException {
+ * List<Double> doubles = new ArrayList<Double>();
+ *
+ * reader.beginArray();
+ * while (reader.hasNext()) {
+ * doubles.add(reader.nextDouble());
+ * }
+ * reader.endArray();
+ * return doubles;
+ * }
+ *
+ * public User readUser(JsonReader reader) throws IOException {
+ * String username = null;
+ * int followersCount = -1;
+ *
+ * reader.beginObject();
+ * while (reader.hasNext()) {
+ * String name = reader.nextName();
+ * if (name.equals("name")) {
+ * username = reader.nextString();
+ * } else if (name.equals("followers_count")) {
+ * followersCount = reader.nextInt();
+ * } else {
+ * reader.skipValue();
+ * }
+ * }
+ * reader.endObject();
+ * return new User(username, followersCount);
+ * }}</pre>
+ *
+ * <h3>Number Handling</h3>
+ * This reader permits numeric values to be read as strings and string values to
+ * be read as numbers. For example, both elements of the JSON array {@code
+ * [1, "1"]} may be read using either {@link #nextInt} or {@link #nextString}.
+ * This behavior is intended to prevent lossy numeric conversions: double is
+ * JavaScript's only numeric type and very large values like {@code
+ * 9007199254740993} cannot be represented exactly on that platform. To minimize
+ * precision loss, extremely large values should be written and read as strings
+ * in JSON.
+ *
+ * <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances
+ * of this class are not thread safe.
+ */
+public final class JsonReader implements Closeable {
+
+ private static final String TRUE = "true";
+ private static final String FALSE = "false";
+
+ /** The input JSON. */
+ private final Reader in;
+
+ /** True to accept non-spec compliant JSON */
+ private boolean lenient = false;
+
+ /**
+ * Use a manual buffer to easily read and unread upcoming characters, and
+ * also so we can create strings without an intermediate StringBuilder.
+ * We decode literals directly out of this buffer, so it must be at least as
+ * long as the longest token that can be reported as a number.
+ */
+ private final char[] buffer = new char[1024];
+ private int pos = 0;
+ private int limit = 0;
+
+ private final List<JsonScope> stack = new ArrayList<JsonScope>();
+ {
+ push(JsonScope.EMPTY_DOCUMENT);
+ }
+
+ /**
+ * The type of the next token to be returned by {@link #peek} and {@link
+ * #advance}. If null, peek() will assign a value.
+ */
+ private JsonToken token;
+
+ /** The text of the next name. */
+ private String name;
+
+ /*
+ * For the next literal value, we may have the text value, or the position
+ * and length in the buffer.
+ */
+ private String value;
+ private int valuePos;
+ private int valueLength;
+
+ /** True if we're currently handling a skipValue() call. */
+ private boolean skipping = false;
+
+ /**
+ * Creates a new instance that reads a JSON-encoded stream from {@code in}.
+ */
+ public JsonReader(Reader in) {
+ if (in == null) {
+ throw new NullPointerException("in == null");
+ }
+ this.in = in;
+ }
+
+ /**
+ * Configure this parser to be be liberal in what it accepts. By default,
+ * this parser is strict and only accepts JSON as specified by <a
+ * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the
+ * parser to lenient causes it to ignore the following syntax errors:
+ *
+ * <ul>
+ * <li>End of line comments starting with {@code //} or {@code #} and
+ * ending with a newline character.
+ * <li>C-style comments starting with {@code /*} and ending with
+ * {@code *}{@code /}. Such comments may not be nested.
+ * <li>Names that are unquoted or {@code 'single quoted'}.
+ * <li>Strings that are unquoted or {@code 'single quoted'}.
+ * <li>Array elements separated by {@code ;} instead of {@code ,}.
+ * <li>Unnecessary array separators. These are interpreted as if null
+ * was the omitted value.
+ * <li>Names and values separated by {@code =} or {@code =>} instead of
+ * {@code :}.
+ * <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
+ * </ul>
+ */
+ public void setLenient(boolean lenient) {
+ this.lenient = lenient;
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * beginning of a new array.
+ */
+ public void beginArray() throws IOException {
+ expect(JsonToken.BEGIN_ARRAY);
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * end of the current array.
+ */
+ public void endArray() throws IOException {
+ expect(JsonToken.END_ARRAY);
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * beginning of a new object.
+ */
+ public void beginObject() throws IOException {
+ expect(JsonToken.BEGIN_OBJECT);
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * end of the current array.
+ */
+ public void endObject() throws IOException {
+ expect(JsonToken.END_OBJECT);
+ }
+
+ /**
+ * Consumes {@code expected}.
+ */
+ private void expect(JsonToken expected) throws IOException {
+ peek();
+ if (token != expected) {
+ throw new IllegalStateException("Expected " + expected + " but was " + peek());
+ }
+ advance();
+ }
+
+ /**
+ * Returns true if the current array or object has another element.
+ */
+ public boolean hasNext() throws IOException {
+ peek();
+ return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY;
+ }
+
+ /**
+ * Returns the type of the next token without consuming it.
+ */
+ public JsonToken peek() throws IOException {
+ if (token != null) {
+ return token;
+ }
+
+ switch (peekStack()) {
+ case EMPTY_DOCUMENT:
+ replaceTop(JsonScope.NONEMPTY_DOCUMENT);
+ JsonToken firstToken = nextValue();
+ if (token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) {
+ throw new IOException(
+ "Expected JSON document to start with '[' or '{' but was " + token);
+ }
+ return firstToken;
+ case EMPTY_ARRAY:
+ return nextInArray(true);
+ case NONEMPTY_ARRAY:
+ return nextInArray(false);
+ case EMPTY_OBJECT:
+ return nextInObject(true);
+ case DANGLING_NAME:
+ return objectValue();
+ case NONEMPTY_OBJECT:
+ return nextInObject(false);
+ case NONEMPTY_DOCUMENT:
+ return token = JsonToken.END_DOCUMENT;
+ case CLOSED:
+ throw new IllegalStateException("JsonReader is closed");
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Advances the cursor in the JSON stream to the next token.
+ */
+ private JsonToken advance() throws IOException {
+ peek();
+
+ JsonToken result = token;
+ token = null;
+ value = null;
+ name = null;
+ return result;
+ }
+
+ /**
+ * Returns the next token, a {@link JsonToken#NAME property name}, and
+ * consumes it.
+ *
+ * @throws IOException if the next token in the stream is not a property
+ * name.
+ */
+ public String nextName() throws IOException {
+ peek();
+ if (token != JsonToken.NAME) {
+ throw new IllegalStateException("Expected a name but was " + peek());
+ }
+ String result = name;
+ advance();
+ return result;
+ }
+
+ /**
+ * Returns the {@link JsonToken#STRING string} value of the next token,
+ * consuming it. If the next token is a number, this method will return its
+ * string form.
+ *
+ * @throws IllegalStateException if the next token is not a string or if
+ * this reader is closed.
+ */
+ public String nextString() throws IOException {
+ peek();
+ if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
+ throw new IllegalStateException("Expected a string but was " + peek());
+ }
+
+ String result = value;
+ advance();
+ return result;
+ }
+
+ /**
+ * Returns the {@link JsonToken#BOOLEAN boolean} value of the next token,
+ * consuming it.
+ *
+ * @throws IllegalStateException if the next token is not a boolean or if
+ * this reader is closed.
+ */
+ public boolean nextBoolean() throws IOException {
+ peek();
+ if (token != JsonToken.BOOLEAN) {
+ throw new IllegalStateException("Expected a boolean but was " + token);
+ }
+
+ boolean result = (value == TRUE);
+ advance();
+ return result;
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is a
+ * literal null.
+ *
+ * @throws IllegalStateException if the next token is not null or if this
+ * reader is closed.
+ */
+ public void nextNull() throws IOException {
+ peek();
+ if (token != JsonToken.NULL) {
+ throw new IllegalStateException("Expected null but was " + token);
+ }
+
+ advance();
+ }
+
+ /**
+ * Returns the {@link JsonToken#NUMBER double} value of the next token,
+ * consuming it. If the next token is a string, this method will attempt to
+ * parse it as a double using {@link Double#parseDouble(String)}.
+ *
+ * @throws IllegalStateException if the next token is not a literal value.
+ */
+ public double nextDouble() throws IOException {
+ peek();
+ if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
+ throw new IllegalStateException("Expected a double but was " + token);
+ }
+
+ double result = Double.parseDouble(value);
+ advance();
+ return result;
+ }
+
+ /**
+ * Returns the {@link JsonToken#NUMBER long} value of the next token,
+ * consuming it. If the next token is a string, this method will attempt to
+ * parse it as a long. If the next token's numeric value cannot be exactly
+ * represented by a Java {@code long}, this method throws.
+ *
+ * @throws IllegalStateException if the next token is not a literal value.
+ * @throws NumberFormatException if the next literal value cannot be parsed
+ * as a number, or exactly represented as a long.
+ */
+ public long nextLong() throws IOException {
+ peek();
+ if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
+ throw new IllegalStateException("Expected a long but was " + token);
+ }
+
+ long result;
+ try {
+ result = Long.parseLong(value);
+ } catch (NumberFormatException ignored) {
+ double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException
+ result = (long) asDouble;
+ if ((double) result != asDouble) {
+ throw new NumberFormatException(value);
+ }
+ }
+
+ advance();
+ return result;
+ }
+
+ /**
+ * Returns the {@link JsonToken#NUMBER int} value of the next token,
+ * consuming it. If the next token is a string, this method will attempt to
+ * parse it as an int. If the next token's numeric value cannot be exactly
+ * represented by a Java {@code int}, this method throws.
+ *
+ * @throws IllegalStateException if the next token is not a literal value.
+ * @throws NumberFormatException if the next literal value cannot be parsed
+ * as a number, or exactly represented as an int.
+ */
+ public int nextInt() throws IOException {
+ peek();
+ if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
+ throw new IllegalStateException("Expected an int but was " + token);
+ }
+
+ int result;
+ try {
+ result = Integer.parseInt(value);
+ } catch (NumberFormatException ignored) {
+ double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException
+ result = (int) asDouble;
+ if ((double) result != asDouble) {
+ throw new NumberFormatException(value);
+ }
+ }
+
+ advance();
+ return result;
+ }
+
+ /**
+ * Closes this JSON reader and the underlying {@link Reader}.
+ */
+ public void close() throws IOException {
+ value = null;
+ token = null;
+ stack.clear();
+ stack.add(JsonScope.CLOSED);
+ in.close();
+ }
+
+ /**
+ * Skips the next value recursively. If it is an object or array, all nested
+ * elements are skipped. This method is intended for use when the JSON token
+ * stream contains unrecognized or unhandled values.
+ */
+ public void skipValue() throws IOException {
+ skipping = true;
+ try {
+ int count = 0;
+ do {
+ JsonToken token = advance();
+ if (token == JsonToken.BEGIN_ARRAY || token == JsonToken.BEGIN_OBJECT) {
+ count++;
+ } else if (token == JsonToken.END_ARRAY || token == JsonToken.END_OBJECT) {
+ count--;
+ }
+ } while (count != 0);
+ } finally {
+ skipping = false;
+ }
+ }
+
+ private JsonScope peekStack() {
+ return stack.get(stack.size() - 1);
+ }
+
+ private JsonScope pop() {
+ return stack.remove(stack.size() - 1);
+ }
+
+ private void push(JsonScope newTop) {
+ stack.add(newTop);
+ }
+
+ /**
+ * Replace the value on the top of the stack with the given value.
+ */
+ private void replaceTop(JsonScope newTop) {
+ stack.set(stack.size() - 1, newTop);
+ }
+
+ private JsonToken nextInArray(boolean firstElement) throws IOException {
+ if (firstElement) {
+ replaceTop(JsonScope.NONEMPTY_ARRAY);
+ } else {
+ /* Look for a comma before each element after the first element. */
+ switch (nextNonWhitespace()) {
+ case ']':
+ pop();
+ return token = JsonToken.END_ARRAY;
+ case ';':
+ checkLenient(); // fall-through
+ case ',':
+ break;
+ default:
+ throw syntaxError("Unterminated array");
+ }
+ }
+
+ switch (nextNonWhitespace()) {
+ case ']':
+ if (firstElement) {
+ pop();
+ return token = JsonToken.END_ARRAY;
+ }
+ // fall-through to handle ",]"
+ case ';':
+ case ',':
+ /* In lenient mode, a 0-length literal means 'null' */
+ checkLenient();
+ pos--;
+ value = "null";
+ return token = JsonToken.NULL;
+ default:
+ pos--;
+ return nextValue();
+ }
+ }
+
+ private JsonToken nextInObject(boolean firstElement) throws IOException {
+ /*
+ * Read delimiters. Either a comma/semicolon separating this and the
+ * previous name-value pair, or a close brace to denote the end of the
+ * object.
+ */
+ if (firstElement) {
+ /* Peek to see if this is the empty object. */
+ switch (nextNonWhitespace()) {
+ case '}':
+ pop();
+ return token = JsonToken.END_OBJECT;
+ default:
+ pos--;
+ }
+ } else {
+ switch (nextNonWhitespace()) {
+ case '}':
+ pop();
+ return token = JsonToken.END_OBJECT;
+ case ';':
+ case ',':
+ break;
+ default:
+ throw syntaxError("Unterminated object");
+ }
+ }
+
+ /* Read the name. */
+ int quote = nextNonWhitespace();
+ switch (quote) {
+ case '\'':
+ checkLenient(); // fall-through
+ case '"':
+ name = nextString((char) quote);
+ break;
+ default:
+ checkLenient();
+ pos--;
+ name = nextLiteral(false);
+ if (name.isEmpty()) {
+ throw syntaxError("Expected name");
+ }
+ }
+
+ replaceTop(JsonScope.DANGLING_NAME);
+ return token = JsonToken.NAME;
+ }
+
+ private JsonToken objectValue() throws IOException {
+ /*
+ * Read the name/value separator. Usually a colon ':'. In lenient mode
+ * we also accept an equals sign '=', or an arrow "=>".
+ */
+ switch (nextNonWhitespace()) {
+ case ':':
+ break;
+ case '=':
+ checkLenient();
+ if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') {
+ pos++;
+ }
+ break;
+ default:
+ throw syntaxError("Expected ':'");
+ }
+
+ replaceTop(JsonScope.NONEMPTY_OBJECT);
+ return nextValue();
+ }
+
+ private JsonToken nextValue() throws IOException {
+ int c = nextNonWhitespace();
+ switch (c) {
+ case '{':
+ push(JsonScope.EMPTY_OBJECT);
+ return token = JsonToken.BEGIN_OBJECT;
+
+ case '[':
+ push(JsonScope.EMPTY_ARRAY);
+ return token = JsonToken.BEGIN_ARRAY;
+
+ case '\'':
+ checkLenient(); // fall-through
+ case '"':
+ value = nextString((char) c);
+ return token = JsonToken.STRING;
+
+ default:
+ pos--;
+ return readLiteral();
+ }
+ }
+
+ /**
+ * Returns true once {@code limit - pos >= minimum}. If the data is
+ * exhausted before that many characters are available, this returns
+ * false.
+ */
+ private boolean fillBuffer(int minimum) throws IOException {
+ if (limit != pos) {
+ limit -= pos;
+ System.arraycopy(buffer, pos, buffer, 0, limit);
+ } else {
+ limit = 0;
+ }
+
+ pos = 0;
+ int total;
+ while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) {
+ limit += total;
+ if (limit >= minimum) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int nextNonWhitespace() throws IOException {
+ while (pos < limit || fillBuffer(1)) {
+ int c = buffer[pos++];
+ switch (c) {
+ case '\t':
+ case ' ':
+ case '\n':
+ case '\r':
+ continue;
+
+ case '/':
+ if (pos == limit && !fillBuffer(1)) {
+ return c;
+ }
+
+ checkLenient();
+ char peek = buffer[pos];
+ switch (peek) {
+ case '*':
+ // skip a /* c-style comment */
+ pos++;
+ if (!skipTo("*/")) {
+ throw syntaxError("Unterminated comment");
+ }
+ pos += 2;
+ continue;
+
+ case '/':
+ // skip a // end-of-line comment
+ pos++;
+ skipToEndOfLine();
+ continue;
+
+ default:
+ return c;
+ }
+
+ case '#':
+ /*
+ * Skip a # hash end-of-line comment. The JSON RFC doesn't
+ * specify this behaviour, but it's required to parse
+ * existing documents. See http://b/2571423.
+ */
+ checkLenient();
+ skipToEndOfLine();
+ continue;
+
+ default:
+ return c;
+ }
+ }
+
+ throw syntaxError("End of input");
+ }
+
+ private void checkLenient() throws IOException {
+ if (!lenient) {
+ throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON");
+ }
+ }
+
+ /**
+ * Advances the position until after the next newline character. If the line
+ * is terminated by "\r\n", the '\n' must be consumed as whitespace by the
+ * caller.
+ */
+ private void skipToEndOfLine() throws IOException {
+ while (pos < limit || fillBuffer(1)) {
+ char c = buffer[pos++];
+ if (c == '\r' || c == '\n') {
+ break;
+ }
+ }
+ }
+
+ private boolean skipTo(String toFind) throws IOException {
+ outer:
+ for (; pos + toFind.length() < limit || fillBuffer(toFind.length()); pos++) {
+ for (int c = 0; c < toFind.length(); c++) {
+ if (buffer[pos + c] != toFind.charAt(c)) {
+ continue outer;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the string up to but not including {@code quote}, unescaping any
+ * character escape sequences encountered along the way. The opening quote
+ * should have already been read. This consumes the closing quote, but does
+ * not include it in the returned string.
+ *
+ * @param quote either ' or ".
+ * @throws NumberFormatException if any unicode escape sequences are
+ * malformed.
+ */
+ private String nextString(char quote) throws IOException {
+ StringBuilder builder = null;
+ do {
+ /* the index of the first character not yet appended to the builder. */
+ int start = pos;
+ while (pos < limit) {
+ int c = buffer[pos++];
+
+ if (c == quote) {
+ if (skipping) {
+ return "skipped!";
+ } else if (builder == null) {
+ return new String(buffer, start, pos - start - 1);
+ } else {
+ builder.append(buffer, start, pos - start - 1);
+ return builder.toString();
+ }
+
+ } else if (c == '\\') {
+ if (builder == null) {
+ builder = new StringBuilder();
+ }
+ builder.append(buffer, start, pos - start - 1);
+ builder.append(readEscapeCharacter());
+ start = pos;
+ }
+ }
+
+ if (builder == null) {
+ builder = new StringBuilder();
+ }
+ builder.append(buffer, start, pos - start);
+ } while (fillBuffer(1));
+
+ throw syntaxError("Unterminated string");
+ }
+
+ /**
+ * Reads the value up to but not including any delimiter characters. This
+ * does not consume the delimiter character.
+ *
+ * @param assignOffsetsOnly true for this method to only set the valuePos
+ * and valueLength fields and return a null result. This only works if
+ * the literal is short; a string is returned otherwise.
+ */
+ private String nextLiteral(boolean assignOffsetsOnly) throws IOException {
+ StringBuilder builder = null;
+ valuePos = -1;
+ valueLength = 0;
+ int i = 0;
+
+ findNonLiteralCharacter:
+ while (true) {
+ for (; pos + i < limit; i++) {
+ switch (buffer[pos + i]) {
+ case '/':
+ case '\\':
+ case ';':
+ case '#':
+ case '=':
+ checkLenient(); // fall-through
+ case '{':
+ case '}':
+ case '[':
+ case ']':
+ case ':':
+ case ',':
+ case ' ':
+ case '\t':
+ case '\f':
+ case '\r':
+ case '\n':
+ break findNonLiteralCharacter;
+ }
+ }
+
+ /*
+ * Attempt to load the entire literal into the buffer at once. If
+ * we run out of input, add a non-literal character at the end so
+ * that decoding doesn't need to do bounds checks.
+ */
+ if (i < buffer.length) {
+ if (fillBuffer(i + 1)) {
+ continue;
+ } else {
+ buffer[limit] = '\0';
+ break;
+ }
+ }
+
+ // use a StringBuilder when the value is too long. It must be an unquoted string.
+ if (builder == null) {
+ builder = new StringBuilder();
+ }
+ builder.append(buffer, pos, i);
+ valueLength += i;
+ pos += i;
+ i = 0;
+ if (!fillBuffer(1)) {
+ break;
+ }
+ }
+
+ String result;
+ if (assignOffsetsOnly && builder == null) {
+ valuePos = pos;
+ result = null;
+ } else if (skipping) {
+ result = "skipped!";
+ } else if (builder == null) {
+ result = new String(buffer, pos, i);
+ } else {
+ builder.append(buffer, pos, i);
+ result = builder.toString();
+ }
+ valueLength += i;
+ pos += i;
+ return result;
+ }
+
+ @Override public String toString() {
+ return getClass().getSimpleName() + " near " + getSnippet();
+ }
+
+ /**
+ * Unescapes the character identified by the character or characters that
+ * immediately follow a backslash. The backslash '\' should have already
+ * been read. This supports both unicode escapes "u000A" and two-character
+ * escapes "\n".
+ *
+ * @throws NumberFormatException if any unicode escape sequences are
+ * malformed.
+ */
+ private char readEscapeCharacter() throws IOException {
+ if (pos == limit && !fillBuffer(1)) {
+ throw syntaxError("Unterminated escape sequence");
+ }
+
+ char escaped = buffer[pos++];
+ switch (escaped) {
+ case 'u':
+ if (pos + 4 > limit && !fillBuffer(4)) {
+ throw syntaxError("Unterminated escape sequence");
+ }
+ String hex = new String(buffer, pos, 4);
+ pos += 4;
+ return (char) Integer.parseInt(hex, 16);
+
+ case 't':
+ return '\t';
+
+ case 'b':
+ return '\b';
+
+ case 'n':
+ return '\n';
+
+ case 'r':
+ return '\r';
+
+ case 'f':
+ return '\f';
+
+ case '\'':
+ case '"':
+ case '\\':
+ default:
+ return escaped;
+ }
+ }
+
+ /**
+ * Reads a null, boolean, numeric or unquoted string literal value.
+ */
+ private JsonToken readLiteral() throws IOException {
+ value = nextLiteral(true);
+ if (valueLength == 0) {
+ throw syntaxError("Expected literal value");
+ }
+ token = decodeLiteral();
+ if (token == JsonToken.STRING) {
+ checkLenient();
+ }
+ return token;
+ }
+
+ /**
+ * Assigns {@code nextToken} based on the value of {@code nextValue}.
+ */
+ private JsonToken decodeLiteral() throws IOException {
+ if (valuePos == -1) {
+ // it was too long to fit in the buffer so it can only be a string
+ return JsonToken.STRING;
+ } else if (valueLength == 4
+ && ('n' == buffer[valuePos ] || 'N' == buffer[valuePos ])
+ && ('u' == buffer[valuePos + 1] || 'U' == buffer[valuePos + 1])
+ && ('l' == buffer[valuePos + 2] || 'L' == buffer[valuePos + 2])
+ && ('l' == buffer[valuePos + 3] || 'L' == buffer[valuePos + 3])) {
+ value = "null";
+ return JsonToken.NULL;
+ } else if (valueLength == 4
+ && ('t' == buffer[valuePos ] || 'T' == buffer[valuePos ])
+ && ('r' == buffer[valuePos + 1] || 'R' == buffer[valuePos + 1])
+ && ('u' == buffer[valuePos + 2] || 'U' == buffer[valuePos + 2])
+ && ('e' == buffer[valuePos + 3] || 'E' == buffer[valuePos + 3])) {
+ value = TRUE;
+ return JsonToken.BOOLEAN;
+ } else if (valueLength == 5
+ && ('f' == buffer[valuePos ] || 'F' == buffer[valuePos ])
+ && ('a' == buffer[valuePos + 1] || 'A' == buffer[valuePos + 1])
+ && ('l' == buffer[valuePos + 2] || 'L' == buffer[valuePos + 2])
+ && ('s' == buffer[valuePos + 3] || 'S' == buffer[valuePos + 3])
+ && ('e' == buffer[valuePos + 4] || 'E' == buffer[valuePos + 4])) {
+ value = FALSE;
+ return JsonToken.BOOLEAN;
+ } else {
+ value = new String(buffer, valuePos, valueLength);
+ return decodeNumber(buffer, valuePos, valueLength);
+ }
+ }
+
+ /**
+ * Determine whether the characters is a JSON number. Numbers are of the
+ * form -12.34e+56. Fractional and exponential parts are optional. Leading
+ * zeroes are not allowed in the value or exponential part, but are allowed
+ * in the fraction.
+ *
+ * <p>This has a side effect of setting isInteger.
+ */
+ private JsonToken decodeNumber(char[] chars, int offset, int length) {
+ int i = offset;
+ int c = chars[i];
+
+ if (c == '-') {
+ c = chars[++i];
+ }
+
+ if (c == '0') {
+ c = chars[++i];
+ } else if (c >= '1' && c <= '9') {
+ c = chars[++i];
+ while (c >= '0' && c <= '9') {
+ c = chars[++i];
+ }
+ } else {
+ return JsonToken.STRING;
+ }
+
+ if (c == '.') {
+ c = chars[++i];
+ while (c >= '0' && c <= '9') {
+ c = chars[++i];
+ }
+ }
+
+ if (c == 'e' || c == 'E') {
+ c = chars[++i];
+ if (c == '+' || c == '-') {
+ c = chars[++i];
+ }
+ if (c >= '0' && c <= '9') {
+ c = chars[++i];
+ while (c >= '0' && c <= '9') {
+ c = chars[++i];
+ }
+ } else {
+ return JsonToken.STRING;
+ }
+ }
+
+ if (i == offset + length) {
+ return JsonToken.NUMBER;
+ } else {
+ return JsonToken.STRING;
+ }
+ }
+
+ /**
+ * Throws a new IO exception with the given message and a context snippet
+ * with this reader's content.
+ */
+ public IOException syntaxError(String message) throws IOException {
+ throw new JsonSyntaxException(message + " near " + getSnippet());
+ }
+
+ private CharSequence getSnippet() {
+ StringBuilder snippet = new StringBuilder();
+ int beforePos = Math.min(pos, 20);
+ snippet.append(buffer, pos - beforePos, beforePos);
+ int afterPos = Math.min(limit - pos, 20);
+ snippet.append(buffer, pos, afterPos);
+ return snippet;
+ }
+
+ private static class JsonSyntaxException extends IOException {
+ private JsonSyntaxException(String s) {
+ super(s);
+ }
+ }
+}
diff --git a/libs/json/src/com/android/json/stream/JsonScope.java b/libs/json/src/com/android/json/stream/JsonScope.java
new file mode 100644
index 0000000..12d10e5
--- /dev/null
+++ b/libs/json/src/com/android/json/stream/JsonScope.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.json.stream;
+
+/**
+ * Lexical scoping elements within a JSON reader or writer.
+ */
+enum JsonScope {
+
+ /**
+ * An array with no elements requires no separators or newlines before
+ * it is closed.
+ */
+ EMPTY_ARRAY,
+
+ /**
+ * A array with at least one value requires a comma and newline before
+ * the next element.
+ */
+ NONEMPTY_ARRAY,
+
+ /**
+ * An object with no name/value pairs requires no separators or newlines
+ * before it is closed.
+ */
+ EMPTY_OBJECT,
+
+ /**
+ * An object whose most recent element is a key. The next element must
+ * be a value.
+ */
+ DANGLING_NAME,
+
+ /**
+ * An object with at least one name/value pair requires a comma and
+ * newline before the next element.
+ */
+ NONEMPTY_OBJECT,
+
+ /**
+ * No object or array has been started.
+ */
+ EMPTY_DOCUMENT,
+
+ /**
+ * A document with at an array or object.
+ */
+ NONEMPTY_DOCUMENT,
+
+ /**
+ * A document that's been closed and cannot be accessed.
+ */
+ CLOSED,
+}
diff --git a/libs/json/src/com/android/json/stream/JsonToken.java b/libs/json/src/com/android/json/stream/JsonToken.java
new file mode 100644
index 0000000..a5233a4
--- /dev/null
+++ b/libs/json/src/com/android/json/stream/JsonToken.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.json.stream;
+
+/**
+ * A structure, name or value type in a JSON-encoded string.
+ */
+public enum JsonToken {
+
+ /**
+ * The opening of a JSON array. Written using {@link JsonWriter#beginObject}
+ * and read using {@link JsonReader#beginObject}.
+ */
+ BEGIN_ARRAY,
+
+ /**
+ * The closing of a JSON array. Written using {@link JsonWriter#endArray}
+ * and read using {@link JsonReader#endArray}.
+ */
+ END_ARRAY,
+
+ /**
+ * The opening of a JSON object. Written using {@link JsonWriter#beginObject}
+ * and read using {@link JsonReader#beginObject}.
+ */
+ BEGIN_OBJECT,
+
+ /**
+ * The closing of a JSON object. Written using {@link JsonWriter#endObject}
+ * and read using {@link JsonReader#endObject}.
+ */
+ END_OBJECT,
+
+ /**
+ * A JSON property name. Within objects, tokens alternate between names and
+ * their values. Written using {@link JsonWriter#name} and read using {@link
+ * JsonReader#nextName}
+ */
+ NAME,
+
+ /**
+ * A JSON string.
+ */
+ STRING,
+
+ /**
+ * A JSON number represented in this API by a Java {@code double}, {@code
+ * long}, or {@code int}.
+ */
+ NUMBER,
+
+ /**
+ * A JSON {@code true} or {@code false}.
+ */
+ BOOLEAN,
+
+ /**
+ * A JSON {@code null}.
+ */
+ NULL,
+
+ /**
+ * The end of the JSON stream. This sentinel value is returned by {@link
+ * JsonReader#peek()} to signal that the JSON-encoded value has no more
+ * tokens.
+ */
+ END_DOCUMENT
+}
diff --git a/libs/json/src/com/android/json/stream/JsonWriter.java b/libs/json/src/com/android/json/stream/JsonWriter.java
new file mode 100644
index 0000000..66b21f0
--- /dev/null
+++ b/libs/json/src/com/android/json/stream/JsonWriter.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.json.stream;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded value to a stream, one token at a time. The stream includes both
+ * literal values (strings, numbers, booleans and nulls) as well as the begin
+ * and end delimiters of objects and arrays.
+ *
+ * <h3>Encoding JSON</h3>
+ * To encode your data as JSON, create a new {@code JsonWriter}. Each JSON
+ * document must contain one top-level array or object. Call methods on the
+ * writer as you walk the structure's contents, nesting arrays and objects as
+ * necessary:
+ * <ul>
+ * <li>To write <strong>arrays</strong>, first call {@link #beginArray()}.
+ * Write each of the array's elements with the appropriate {@link #value}
+ * methods or by nesting other arrays and objects. Finally close the array
+ * using {@link #endArray()}.
+ * <li>To write <strong>objects</strong>, first call {@link #beginObject()}.
+ * Write each of the object's properties by alternating calls to
+ * {@link #name} with the property's value. Write property values with the
+ * appropriate {@link #value} method or by nesting other objects or arrays.
+ * Finally close the object using {@link #endObject()}.
+ * </ul>
+ *
+ * <h3>Example</h3>
+ * Suppose we'd like to encode a stream of messages such as the following: <pre> {@code
+ * [
+ * {
+ * "id": 912345678901,
+ * "text": "How do I write JSON on Android?",
+ * "geo": null,
+ * "user": {
+ * "name": "android_newb",
+ * "followers_count": 41
+ * }
+ * },
+ * {
+ * "id": 912345678902,
+ * "text": "@android_newb just use android.util.JsonWriter!",
+ * "geo": [50.454722, -104.606667],
+ * "user": {
+ * "name": "jesse",
+ * "followers_count": 2
+ * }
+ * }
+ * ]}</pre>
+ * This code encodes the above structure: <pre> {@code
+ * public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
+ * JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
+ * writer.setIndent(" ");
+ * writeMessagesArray(writer, messages);
+ * writer.close();
+ * }
+ *
+ * public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
+ * writer.beginArray();
+ * for (Message message : messages) {
+ * writeMessage(writer, message);
+ * }
+ * writer.endArray();
+ * }
+ *
+ * public void writeMessage(JsonWriter writer, Message message) throws IOException {
+ * writer.beginObject();
+ * writer.name("id").value(message.getId());
+ * writer.name("text").value(message.getText());
+ * if (message.getGeo() != null) {
+ * writer.name("geo");
+ * writeDoublesArray(writer, message.getGeo());
+ * } else {
+ * writer.name("geo").nullValue();
+ * }
+ * writer.name("user");
+ * writeUser(writer, message.getUser());
+ * writer.endObject();
+ * }
+ *
+ * public void writeUser(JsonWriter writer, User user) throws IOException {
+ * writer.beginObject();
+ * writer.name("name").value(user.getName());
+ * writer.name("followers_count").value(user.getFollowersCount());
+ * writer.endObject();
+ * }
+ *
+ * public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
+ * writer.beginArray();
+ * for (Double value : doubles) {
+ * writer.value(value);
+ * }
+ * writer.endArray();
+ * }}</pre>
+ *
+ * <p>Each {@code JsonWriter} may be used to write a single JSON stream.
+ * Instances of this class are not thread safe. Calls that would result in a
+ * malformed JSON string will fail with an {@link IllegalStateException}.
+ */
+public final class JsonWriter implements Closeable {
+
+ /** The output data, containing at most one top-level array or object. */
+ private final Writer out;
+
+ private final List<JsonScope> stack = new ArrayList<JsonScope>();
+ {
+ stack.add(JsonScope.EMPTY_DOCUMENT);
+ }
+
+ /**
+ * A string containing a full set of spaces for a single level of
+ * indentation, or null for no pretty printing.
+ */
+ private String indent;
+
+ /**
+ * The name/value separator; either ":" or ": ".
+ */
+ private String separator = ":";
+
+ /**
+ * Creates a new instance that writes a JSON-encoded stream to {@code out}.
+ * For best performance, ensure {@link Writer} is buffered; wrapping in
+ * {@link java.io.BufferedWriter BufferedWriter} if necessary.
+ */
+ public JsonWriter(Writer out) {
+ if (out == null) {
+ throw new NullPointerException("out == null");
+ }
+ this.out = out;
+ }
+
+ /**
+ * Sets the indentation string to be repeated for each level of indentation
+ * in the encoded document. If {@code indent.isEmpty()} the encoded document
+ * will be compact. Otherwise the encoded document will be more
+ * human-readable.
+ *
+ * @param indent a string containing only whitespace.
+ */
+ public void setIndent(String indent) {
+ if (indent.isEmpty()) {
+ this.indent = null;
+ this.separator = ":";
+ } else {
+ this.indent = indent;
+ this.separator = ": ";
+ }
+ }
+
+ /**
+ * Begins encoding a new array. Each call to this method must be paired with
+ * a call to {@link #endArray}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter beginArray() throws IOException {
+ return open(JsonScope.EMPTY_ARRAY, "[");
+ }
+
+ /**
+ * Ends encoding the current array.
+ *
+ * @return this writer.
+ */
+ public JsonWriter endArray() throws IOException {
+ return close(JsonScope.EMPTY_ARRAY, JsonScope.NONEMPTY_ARRAY, "]");
+ }
+
+ /**
+ * Begins encoding a new object. Each call to this method must be paired
+ * with a call to {@link #endObject}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter beginObject() throws IOException {
+ return open(JsonScope.EMPTY_OBJECT, "{");
+ }
+
+ /**
+ * Ends encoding the current object.
+ *
+ * @return this writer.
+ */
+ public JsonWriter endObject() throws IOException {
+ return close(JsonScope.EMPTY_OBJECT, JsonScope.NONEMPTY_OBJECT, "}");
+ }
+
+ /**
+ * Enters a new scope by appending any necessary whitespace and the given
+ * bracket.
+ */
+ private JsonWriter open(JsonScope empty, String openBracket) throws IOException {
+ beforeValue(true);
+ stack.add(empty);
+ out.write(openBracket);
+ return this;
+ }
+
+ /**
+ * Closes the current scope by appending any necessary whitespace and the
+ * given bracket.
+ */
+ private JsonWriter close(JsonScope empty, JsonScope nonempty, String closeBracket)
+ throws IOException {
+ JsonScope context = peek();
+ if (context != nonempty && context != empty) {
+ throw new IllegalStateException("Nesting problem: " + stack);
+ }
+
+ stack.remove(stack.size() - 1);
+ if (context == nonempty) {
+ newline();
+ }
+ out.write(closeBracket);
+ return this;
+ }
+
+ /**
+ * Returns the value on the top of the stack.
+ */
+ private JsonScope peek() {
+ return stack.get(stack.size() - 1);
+ }
+
+ /**
+ * Replace the value on the top of the stack with the given value.
+ */
+ private void replaceTop(JsonScope topOfStack) {
+ stack.set(stack.size() - 1, topOfStack);
+ }
+
+ /**
+ * Encodes the property name.
+ *
+ * @param name the name of the forthcoming value. May not be null.
+ * @return this writer.
+ */
+ public JsonWriter name(String name) throws IOException {
+ if (name == null) {
+ throw new NullPointerException("name == null");
+ }
+ beforeName();
+ string(name);
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @param value the literal string value, or null to encode a null literal.
+ * @return this writer.
+ */
+ public JsonWriter value(String value) throws IOException {
+ if (value == null) {
+ return nullValue();
+ }
+ beforeValue(false);
+ string(value);
+ return this;
+ }
+
+ /**
+ * Encodes {@code null}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter nullValue() throws IOException {
+ beforeValue(false);
+ out.write("null");
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter value(boolean value) throws IOException {
+ beforeValue(false);
+ out.write(value ? "true" : "false");
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+ * {@link Double#isInfinite() infinities}.
+ * @return this writer.
+ */
+ public JsonWriter value(double value) throws IOException {
+ if (Double.isNaN(value) || Double.isInfinite(value)) {
+ throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
+ }
+ beforeValue(false);
+ out.append(Double.toString(value));
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter value(long value) throws IOException {
+ beforeValue(false);
+ out.write(Long.toString(value));
+ return this;
+ }
+
+ /**
+ * Ensures all buffered data is written to the underlying {@link Writer}
+ * and flushes that writer.
+ */
+ public void flush() throws IOException {
+ out.flush();
+ }
+
+ /**
+ * Flushes and closes this writer and the underlying {@link Writer}.
+ *
+ * @throws IOException if the JSON document is incomplete.
+ */
+ public void close() throws IOException {
+ out.close();
+
+ if (peek() != JsonScope.NONEMPTY_DOCUMENT) {
+ throw new IOException("Incomplete document");
+ }
+ }
+
+ private void string(String value) throws IOException {
+ out.write("\"");
+ for (int i = 0, length = value.length(); i < length; i++) {
+ char c = value.charAt(i);
+
+ /*
+ * From RFC 4627, "All Unicode characters may be placed within the
+ * quotation marks except for the characters that must be escaped:
+ * quotation mark, reverse solidus, and the control characters
+ * (U+0000 through U+001F)."
+ */
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ out.write('\\');
+ out.write(c);
+ break;
+
+ case '\t':
+ out.write("\\t");
+ break;
+
+ case '\b':
+ out.write("\\b");
+ break;
+
+ case '\n':
+ out.write("\\n");
+ break;
+
+ case '\r':
+ out.write("\\r");
+ break;
+
+ case '\f':
+ out.write("\\f");
+ break;
+
+ default:
+ if (c <= 0x1F) {
+ out.write(String.format("\\u%04x", (int) c));
+ } else {
+ out.write(c);
+ }
+ break;
+ }
+
+ }
+ out.write("\"");
+ }
+
+ private void newline() throws IOException {
+ if (indent == null) {
+ return;
+ }
+
+ out.write("\n");
+ for (int i = 1; i < stack.size(); i++) {
+ out.write(indent);
+ }
+ }
+
+ /**
+ * Inserts any necessary separators and whitespace before a name. Also
+ * adjusts the stack to expect the name's value.
+ */
+ private void beforeName() throws IOException {
+ JsonScope context = peek();
+ if (context == JsonScope.NONEMPTY_OBJECT) { // first in object
+ out.write(',');
+ } else if (context != JsonScope.EMPTY_OBJECT) { // not in an object!
+ throw new IllegalStateException("Nesting problem: " + stack);
+ }
+ newline();
+ replaceTop(JsonScope.DANGLING_NAME);
+ }
+
+ /**
+ * Inserts any necessary separators and whitespace before a literal value,
+ * inline array, or inline object. Also adjusts the stack to expect either a
+ * closing bracket or another element.
+ *
+ * @param root true if the value is a new array or object, the two values
+ * permitted as top-level elements.
+ */
+ private void beforeValue(boolean root) throws IOException {
+ switch (peek()) {
+ case EMPTY_DOCUMENT: // first in document
+ if (!root) {
+ throw new IllegalStateException(
+ "JSON must start with an array or an object.");
+ }
+ replaceTop(JsonScope.NONEMPTY_DOCUMENT);
+ break;
+
+ case EMPTY_ARRAY: // first in array
+ replaceTop(JsonScope.NONEMPTY_ARRAY);
+ newline();
+ break;
+
+ case NONEMPTY_ARRAY: // another in array
+ out.append(',');
+ newline();
+ break;
+
+ case DANGLING_NAME: // value for name
+ out.append(separator);
+ replaceTop(JsonScope.NONEMPTY_OBJECT);
+ break;
+
+ case NONEMPTY_DOCUMENT:
+ throw new IllegalStateException(
+ "JSON must have only one top-level value.");
+
+ default:
+ throw new IllegalStateException("Nesting problem: " + stack);
+ }
+ }
+}
diff --git a/libs/vogar-expect/Android.mk b/libs/vogar-expect/Android.mk
new file mode 100644
index 0000000..075bb43
--- /dev/null
+++ b/libs/vogar-expect/Android.mk
@@ -0,0 +1,25 @@
+#
+# Copyright (C) 2010 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MODULE := vogarexpectlib
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := guavalib jsonlib
+include $(BUILD_HOST_JAVA_LIBRARY)
+
diff --git a/libs/vogar-expect/README b/libs/vogar-expect/README
new file mode 100644
index 0000000..eee6f83
--- /dev/null
+++ b/libs/vogar-expect/README
@@ -0,0 +1 @@
+Selected classes taken from http://code.google.com/p/vogar/
diff --git a/libs/vogar-expect/src/vogar/AnnotatedOutcome.java b/libs/vogar-expect/src/vogar/AnnotatedOutcome.java
new file mode 100644
index 0000000..a27ab9e
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/AnnotatedOutcome.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2010 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 vogar;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedMap;
+
+/**
+ * Contains an outcome for a test, along with some metadata pertaining to the history of this test,
+ * including a list of previous outcomes, an outcome corresponding to the tag Vogar is being run
+ * with, if applicable, and the expectation for this test, so that result value information is
+ * available.
+ */
+public final class AnnotatedOutcome {
+ public static Ordering<AnnotatedOutcome> ORDER_BY_NAME = new Ordering<AnnotatedOutcome>() {
+ @Override public int compare(AnnotatedOutcome a, AnnotatedOutcome b) {
+ return a.getName().compareTo(b.getName());
+ }
+ };
+
+ private final Expectation expectation;
+ private final Outcome outcome;
+ /** a list of previous outcomes for the same action, sorted in chronological order */
+ private final SortedMap<Long, Outcome> previousOutcomes;
+ /** will be null if not comparing to a tag */
+ private final String tagName;
+ private final Outcome tagOutcome;
+ private final boolean hasMetadata;
+
+ AnnotatedOutcome(Outcome outcome, Expectation expectation,
+ SortedMap<Long, Outcome> previousOutcomes, String tagName, Outcome tagOutcome,
+ boolean hasMetadata) {
+ if (previousOutcomes == null) {
+ throw new NullPointerException();
+ }
+ this.expectation = expectation;
+ this.outcome = outcome;
+ this.previousOutcomes = previousOutcomes;
+ this.tagName = tagName;
+ this.tagOutcome = tagOutcome;
+ this.hasMetadata = hasMetadata;
+ }
+
+ public Outcome getOutcome() {
+ return outcome;
+ }
+
+ public String getName() {
+ return outcome.getName();
+ }
+
+ public ResultValue getResultValue() {
+ return outcome.getResultValue(expectation);
+ }
+
+ public List<ResultValue> getPreviousResultValues() {
+ List<ResultValue> previousResultValues = new ArrayList<ResultValue>();
+ for (Outcome previousOutcome : previousOutcomes.values()) {
+ previousResultValues.add(previousOutcome.getResultValue(expectation));
+ }
+ return previousResultValues;
+ }
+
+ /**
+ * Returns the most recent result value of a run of this test (before the current run).
+ */
+ public ResultValue getMostRecentResultValue(ResultValue defaultValue) {
+ List<ResultValue> previousResultValues = getPreviousResultValues();
+ return previousResultValues.isEmpty() ?
+ defaultValue :
+ previousResultValues.get(previousResultValues.size() - 1);
+ }
+
+ public boolean hasTag() {
+ return tagOutcome != null;
+ }
+
+ public String getTagName() {
+ return tagName;
+ }
+
+ public ResultValue getTagResultValue() {
+ return tagOutcome == null ? null : tagOutcome.getResultValue(expectation);
+ }
+
+ /**
+ * Returns true if the outcome is noteworthy given the result value and previous history.
+ */
+ public boolean isNoteworthy() {
+ return getResultValue() != ResultValue.OK || recentlyChanged() || changedSinceTag();
+ }
+
+ public boolean outcomeChanged() {
+ List<Outcome> previousOutcomesList = getOutcomeList();
+ return previousOutcomesList.isEmpty()
+ || !outcome.equals(previousOutcomesList.get(previousOutcomesList.size() - 1));
+ }
+
+ private ArrayList<Outcome> getOutcomeList() {
+ return new ArrayList<Outcome>(previousOutcomes.values());
+ }
+
+ /**
+ * Returns true if the outcome recently changed in result value.
+ */
+ private boolean recentlyChanged() {
+ List<ResultValue> previousResultValues = getPreviousResultValues();
+ if (previousResultValues.isEmpty()) {
+ return false;
+ }
+ return previousResultValues.get(previousResultValues.size() - 1) != getResultValue();
+ }
+
+ private boolean changedSinceTag() {
+ ResultValue tagResultValue = getTagResultValue();
+ return tagResultValue != null && tagResultValue != getResultValue();
+ }
+
+ /**
+ * Returns a Long representing the time the outcome was last run. Returns {@code defaultValue}
+ * if the outcome is not known to have run before.
+ */
+ public Long lastRun(Long defaultValue) {
+ if (!hasMetadata) {
+ return defaultValue;
+ }
+ List<Long> runTimes = Lists.newArrayList(previousOutcomes.keySet());
+ return runTimes.isEmpty() ? defaultValue : runTimes.get(runTimes.size() - 1);
+ }
+}
diff --git a/libs/vogar-expect/src/vogar/Expectation.java b/libs/vogar-expect/src/vogar/Expectation.java
new file mode 100644
index 0000000..f065f42
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/Expectation.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2010 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 vogar;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * The expected result of an action execution. This is typically encoded in the
+ * expectations text file, which has the following format:
+ * <pre>
+ * test java.io.StreamTokenizer.Reset
+ * result UNSUPPORTED
+ * pattern .*should get token \[, but get -1.*
+ *
+ * # should we fix this?
+ * test java.util.Arrays.CopyMethods
+ * result COMPILE_FAILED
+ * pattern .*cannot find symbol.*
+ * </pre>
+ */
+public final class Expectation {
+
+ /** The pattern to use when no expected output is specified */
+ public static final Pattern MATCH_ALL_PATTERN
+ = Pattern.compile(".*", Pattern.MULTILINE | Pattern.DOTALL);
+
+ /** The expectation of a general successful run. */
+ public static final Expectation SUCCESS = new Expectation(Result.SUCCESS, MATCH_ALL_PATTERN,
+ Collections.<String>emptySet(), "", -1);
+
+ /** Justification for this expectation */
+ private final String description;
+
+ /** The action's expected result, such as {@code EXEC_FAILED}. */
+ private final Result result;
+
+ /** The pattern the expected output will match. */
+ private final Pattern pattern;
+
+ /** Attributes of this test. */
+ private final Set<String> tags;
+
+ /** The tracking bug ID */
+ private final long bug;
+
+ /** True if the identified bug still active. */
+ private boolean bugIsOpen = false;
+
+ public Expectation(Result result, Pattern pattern, Set<String> tags, String description, long bug) {
+ if (result == null || description == null || pattern == null) {
+ throw new IllegalArgumentException(
+ "result=" + result + " description=" + description + " pattern=" + pattern);
+ }
+
+ this.description = description;
+ this.result = result;
+ this.pattern = pattern;
+ this.tags = new LinkedHashSet<String>(tags);
+ this.bug = bug;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public long getBug() {
+ return bug;
+ }
+
+ public Result getResult() {
+ return result;
+ }
+
+ public Set<String> getTags() {
+ return tags;
+ }
+
+ /**
+ * Set the current status of this expectation's bug. When a bug is open,
+ * any result (success or failure) is permitted.
+ */
+ public void setBugIsOpen(boolean bugIsOpen) {
+ this.bugIsOpen = bugIsOpen;
+ }
+
+ /**
+ * Returns true if {@code outcome} matches this expectation.
+ */
+ public boolean matches(Outcome outcome) {
+ return patternMatches(outcome) && (bugIsOpen || result == outcome.getResult());
+ }
+
+ private boolean patternMatches(Outcome outcome) {
+ return pattern.matcher(outcome.getOutput()).matches();
+ }
+
+ @Override public String toString() {
+ return "Expectation[description=" + description + " pattern=" + pattern.pattern() + "]";
+ }
+}
diff --git a/libs/vogar-expect/src/vogar/ExpectationStore.java b/libs/vogar-expect/src/vogar/ExpectationStore.java
new file mode 100644
index 0000000..cfa20e9
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/ExpectationStore.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2010 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 vogar;
+
+//import com.google.caliper.internal.gson.stream.JsonReader;
+
+import com.android.json.stream.JsonReader;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import vogar.commands.Command;
+import vogar.util.Log;
+
+/**
+ * A database of expected outcomes. Entries in this database come in two forms.
+ * <ul>
+ * <li>Outcome expectations name an outcome (or its prefix, such as
+ * "java.util"), its expected result, and an optional pattern to match
+ * the expected output.
+ * <li>Failure expectations include a pattern that may match the output of any
+ * outcome. These expectations are useful for hiding failures caused by
+ * cross-cutting features that aren't supported.
+ * </ul>
+ *
+ * <p>If an outcome matches both an outcome expectation and a failure
+ * expectation, the outcome expectation will be returned.
+ */
+public final class ExpectationStore {
+ private static final int PATTERN_FLAGS = Pattern.MULTILINE | Pattern.DOTALL;
+ private final Map<String, Expectation> outcomes = new LinkedHashMap<String, Expectation>();
+ private final Map<String, Expectation> failures = new LinkedHashMap<String, Expectation>();
+
+ private ExpectationStore() {}
+
+ /**
+ * Finds the expected result for the specified action or outcome name. This
+ * returns a value for all names, even if no explicit expectation was set.
+ */
+ public Expectation get(String name) {
+ Expectation byName = getByNameOrPackage(name);
+ return byName != null ? byName : Expectation.SUCCESS;
+ }
+
+ /**
+ * Finds the expected result for the specified outcome after it has
+ * completed. Unlike {@code get()}, this also takes into account the
+ * outcome's output.
+ *
+ * <p>For outcomes that have both a name match and an output match,
+ * exact name matches are preferred, then output matches, then inexact
+ * name matches.
+ */
+ public Expectation get(Outcome outcome) {
+ Expectation exactNameMatch = outcomes.get(outcome.getName());
+ if (exactNameMatch != null) {
+ return exactNameMatch;
+ }
+
+ for (Map.Entry<String, Expectation> entry : failures.entrySet()) {
+ if (entry.getValue().matches(outcome)) {
+ return entry.getValue();
+ }
+ }
+
+ Expectation byName = getByNameOrPackage(outcome.getName());
+ return byName != null ? byName : Expectation.SUCCESS;
+ }
+
+ private Expectation getByNameOrPackage(String name) {
+ while (true) {
+ Expectation expectation = outcomes.get(name);
+ if (expectation != null) {
+ return expectation;
+ }
+
+ int dotOrHash = Math.max(name.lastIndexOf('.'), name.lastIndexOf('#'));
+ if (dotOrHash == -1) {
+ return null;
+ }
+
+ name = name.substring(0, dotOrHash);
+ }
+ }
+
+ public static ExpectationStore parse(Set<File> expectationFiles, ModeId mode) throws IOException {
+ ExpectationStore result = new ExpectationStore();
+ for (File f : expectationFiles) {
+ if (f.exists()) {
+ result.parse(f, mode);
+ }
+ }
+ return result;
+ }
+
+ public void parse(File expectationsFile, ModeId mode) throws IOException {
+ Log.verbose("loading expectations file " + expectationsFile);
+
+ int count = 0;
+ JsonReader reader = null;
+ try {
+ reader = new JsonReader(new FileReader(expectationsFile));
+ reader.setLenient(true);
+ reader.beginArray();
+ while (reader.hasNext()) {
+ readExpectation(reader, mode);
+ count++;
+ }
+ reader.endArray();
+
+ Log.verbose("loaded " + count + " expectations from " + expectationsFile);
+ } finally {
+ if (reader != null) {
+ reader.close();
+ }
+ }
+ }
+
+ private void readExpectation(JsonReader reader, ModeId mode) throws IOException {
+ boolean isFailure = false;
+ Result result = Result.SUCCESS;
+ Pattern pattern = Expectation.MATCH_ALL_PATTERN;
+ Set<String> names = new LinkedHashSet<String>();
+ Set<String> tags = new LinkedHashSet<String>();
+ Set<ModeId> modes = null;
+ String description = "";
+ long buganizerBug = -1;
+
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+ if (name.equals("result")) {
+ result = Result.valueOf(reader.nextString());
+ } else if (name.equals("name")) {
+ names.add(reader.nextString());
+ } else if (name.equals("names")) {
+ readStrings(reader, names);
+ } else if (name.equals("failure")) {
+ isFailure = true;
+ names.add(reader.nextString());
+ } else if (name.equals("pattern")) {
+ pattern = Pattern.compile(reader.nextString(), PATTERN_FLAGS);
+ } else if (name.equals("substring")) {
+ pattern = Pattern.compile(".*" + Pattern.quote(reader.nextString()) + ".*", PATTERN_FLAGS);
+ } else if (name.equals("tags")) {
+ readStrings(reader, tags);
+ } else if (name.equals("description")) {
+ Iterable<String> split = Splitter.on("\n").omitEmptyStrings().trimResults().split(reader.nextString());
+ description = Joiner.on("\n").join(split);
+ } else if (name.equals("bug")) {
+ buganizerBug = reader.nextLong();
+ } else if (name.equals("modes")) {
+ modes = readModes(reader);
+ } else {
+ Log.warn("Unhandled name in expectations file: " + name);
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+
+ if (names.isEmpty()) {
+ throw new IllegalArgumentException("Missing 'name' or 'failure' key in " + reader);
+ }
+ if (modes != null && !modes.contains(mode)) {
+ return;
+ }
+
+ Expectation expectation = new Expectation(result, pattern, tags, description, buganizerBug);
+ Map<String, Expectation> map = isFailure ? failures : outcomes;
+ for (String name : names) {
+ if (map.put(name, expectation) != null) {
+ throw new IllegalArgumentException("Duplicate expectations for " + name);
+ }
+ }
+ }
+
+ private void readStrings(JsonReader reader, Set<String> output) throws IOException {
+ reader.beginArray();
+ while (reader.hasNext()) {
+ output.add(reader.nextString());
+ }
+ reader.endArray();
+ }
+
+ private Set<ModeId> readModes(JsonReader reader) throws IOException {
+ Set<ModeId> result = new LinkedHashSet<ModeId>();
+ reader.beginArray();
+ while (reader.hasNext()) {
+ result.add(ModeId.valueOf(reader.nextString().toUpperCase()));
+ }
+ reader.endArray();
+ return result;
+ }
+
+ /**
+ * Sets the bugIsOpen status on all expectations by querying an external bug
+ * tracker.
+ */
+ public void loadBugStatuses(String openBugsCommand) {
+ Iterable<Expectation> allExpectations = Iterables.concat(outcomes.values(), failures.values());
+
+ // figure out what bug IDs we're interested in
+ Set<String> bugs = new LinkedHashSet<String>();
+ for (Expectation expectation : allExpectations) {
+ if (expectation.getBug() != -1) {
+ bugs.add(Long.toString(expectation.getBug()));
+ }
+ }
+ if (bugs.isEmpty()) {
+ return;
+ }
+
+ // query the external app for open bugs
+ List<String> openBugs = new Command.Builder()
+ .args(openBugsCommand)
+ .args(bugs)
+ .execute();
+ Set<Long> openBugsSet = new LinkedHashSet<Long>();
+ for (String bug : openBugs) {
+ openBugsSet.add(Long.parseLong(bug));
+ }
+
+ Log.verbose("tracking " + openBugsSet.size() + " open bugs: " + openBugs);
+
+ // update our expectations with that set
+ for (Expectation expectation : allExpectations) {
+ if (openBugsSet.contains(expectation.getBug())) {
+ expectation.setBugIsOpen(true);
+ }
+ }
+ }
+}
diff --git a/libs/vogar-expect/src/vogar/ModeId.java b/libs/vogar-expect/src/vogar/ModeId.java
new file mode 100644
index 0000000..3b24cc1
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/ModeId.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2009 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 vogar;
+
+public enum ModeId {
+ DEVICE, JVM, ACTIVITY, SIM, HOST;
+
+ public boolean acceptsVmArgs() {
+ return this != ACTIVITY;
+ }
+
+ public boolean isHost() {
+ return this == JVM || this == SIM || this == HOST;
+ }
+
+ public boolean requiresAndroidSdk() {
+ return this == DEVICE || this == ACTIVITY || this == SIM || this == HOST;
+ }
+}
diff --git a/libs/vogar-expect/src/vogar/Outcome.java b/libs/vogar-expect/src/vogar/Outcome.java
new file mode 100644
index 0000000..3d7c68f
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/Outcome.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2010 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 vogar;
+
+import com.google.common.collect.Lists;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import vogar.util.Strings;
+
+/**
+ * An outcome of an action. Some actions may have multiple outcomes. For
+ * example, JUnit tests have one outcome for each test method.
+ */
+public final class Outcome {
+
+ private final String outcomeName;
+ private final Result result;
+ private final String output;
+ private final Date date;
+
+ public Outcome(String outcomeName, Result result, List<String> outputLines) {
+ this.outcomeName = outcomeName;
+ this.result = result;
+ this.output = sanitizeOutputLines(outputLines);
+ this.date = new Date();
+ }
+
+ public Outcome(String outcomeName, Result result, String outputLine, Date date) {
+ this.outcomeName = outcomeName;
+ this.result = result;
+ this.output = sanitizeOutputLine(outputLine);
+ this.date = date;
+ }
+
+ public Outcome(String outcomeName, Result result, String outputLine) {
+ this.outcomeName = outcomeName;
+ this.result = result;
+ this.output = sanitizeOutputLine(outputLine);
+ this.date = new Date();
+ }
+
+ public Outcome(String outcomeName, Result result, Throwable throwable) {
+ this.outcomeName = outcomeName;
+ this.result = result;
+ this.output = sanitizeOutputLines(throwableToLines(throwable));
+ this.date = new Date();
+ }
+
+ private String sanitizeOutputLines(List<String> outputLines) {
+ List<String> sanitizedStrings = Lists.newArrayList();
+ for (String line : outputLines) {
+ sanitizedStrings.add(sanitizeOutputLine(line));
+ }
+ return Strings.join(sanitizedStrings, "\n");
+ }
+
+ private String sanitizeOutputLine(String outputLine) {
+ return Strings.xmlSanitize(outputLine.replaceAll("\r\n?", "\n"));
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public String getName() {
+ return outcomeName;
+ }
+
+ public Result getResult() {
+ return result;
+ }
+
+ public String getOutput() {
+ return output;
+ }
+
+ public List<String> getOutputLines() {
+ return Arrays.asList(output.split("\n"));
+ }
+
+ private static List<String> throwableToLines(Throwable t) {
+ StringWriter writer = new StringWriter();
+ PrintWriter out = new PrintWriter(writer);
+ t.printStackTrace(out);
+ return Arrays.asList(writer.toString().split("\\n"));
+ }
+
+ /**
+ * Returns the action's suite name, such as java.lang.Integer or
+ * java.lang.IntegerTest.
+ */
+ public String getSuiteName() {
+ int split = split(outcomeName);
+ return split == -1 ? "defaultpackage" : outcomeName.substring(0, split);
+ }
+
+ /**
+ * Returns the specific action name, such as BitTwiddle or testBitTwiddle.
+ */
+ public String getTestName() {
+ int split = split(outcomeName);
+ return split == -1 ? outcomeName : outcomeName.substring(split + 1);
+ }
+
+ private static int split(String name) {
+ int lastHash = name.indexOf('#');
+ return lastHash == -1 ? name.lastIndexOf('.') : lastHash;
+ }
+
+ /**
+ * Returns whether the result indicates that the contents of the Outcome are important.
+ *
+ * For example, for a test skipped because it is unsupported, we don't care about the result.
+ */
+ private boolean matters() {
+ return result != Result.UNSUPPORTED;
+ }
+
+ public ResultValue getResultValue(Expectation expectation) {
+ if (matters()) {
+ return expectation.matches(this) ? ResultValue.OK : ResultValue.FAIL;
+ }
+ return ResultValue.IGNORE;
+ }
+
+ /**
+ * Returns a filesystem db path for this outcome. For example, a path for an outcome with name
+ * "foo.bar.baz#testName" would be "foo/bar/baz/testName".
+ */
+ public String getPath() {
+ return outcomeName.replaceAll("[\\.#]", "/");
+ }
+
+ @Override public boolean equals(Object o) {
+ if (o instanceof Outcome) {
+ Outcome outcome = (Outcome) o;
+ return outcomeName.equals(outcome.outcomeName)
+ && result == outcome.result
+ && output.equals(outcome.output);
+ }
+ return false;
+ }
+
+ @Override public int hashCode() {
+ int hashCode = 17;
+ hashCode = 37 * hashCode + outcomeName.hashCode();
+ hashCode = 37 * hashCode + result.hashCode();
+ hashCode = 37 * hashCode + output.hashCode();
+ return hashCode;
+ }
+
+ @Override public String toString() {
+ return "Outcome[name=" + outcomeName + " output=" + output + "]";
+ }
+
+}
diff --git a/libs/vogar-expect/src/vogar/Result.java b/libs/vogar-expect/src/vogar/Result.java
new file mode 100644
index 0000000..45c88ce
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/Result.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2009 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 vogar;
+
+/**
+ * The result of a test or benchmark execution.
+ */
+public enum Result {
+
+ /**
+ * An action that cannot be run by this harness, such as a shell script.
+ */
+ UNSUPPORTED,
+
+ COMPILE_FAILED,
+ EXEC_FAILED,
+ EXEC_TIMEOUT,
+ ERROR,
+ SUCCESS
+}
diff --git a/libs/vogar-expect/src/vogar/ResultValue.java b/libs/vogar-expect/src/vogar/ResultValue.java
new file mode 100644
index 0000000..2e450f4
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/ResultValue.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 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 vogar;
+
+/**
+ * Represents an evaluation of the goodness of a result.
+ */
+public enum ResultValue {
+ OK,
+ IGNORE,
+ FAIL
+}
diff --git a/libs/vogar-expect/src/vogar/commands/Command.java b/libs/vogar-expect/src/vogar/commands/Command.java
new file mode 100644
index 0000000..d60d77e
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/commands/Command.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2009 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 vogar.commands;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import vogar.util.Log;
+import vogar.util.Strings;
+import vogar.util.Threads;
+
+/**
+ * An out of process executable.
+ */
+public final class Command {
+ private final List<String> args;
+ private final Map<String, String> env;
+ private final File workingDirectory;
+ private final boolean permitNonZeroExitStatus;
+ private final PrintStream tee;
+ private final boolean nativeOutput;
+ private volatile Process process;
+
+ public Command(String... args) {
+ this(Arrays.asList(args));
+ }
+
+ public Command(List<String> args) {
+ this.args = new ArrayList<String>(args);
+ this.env = Collections.emptyMap();
+ this.workingDirectory = null;
+ this.permitNonZeroExitStatus = false;
+ this.tee = null;
+ this.nativeOutput = false;
+ }
+
+ private Command(Builder builder) {
+ this.args = new ArrayList<String>(builder.args);
+ this.env = builder.env;
+ this.workingDirectory = builder.workingDirectory;
+ this.permitNonZeroExitStatus = builder.permitNonZeroExitStatus;
+ this.tee = builder.tee;
+ if (builder.maxLength != -1) {
+ String string = toString();
+ if (string.length() > builder.maxLength) {
+ throw new IllegalStateException("Maximum command length " + builder.maxLength
+ + " exceeded by: " + string);
+ }
+ }
+ this.nativeOutput = builder.nativeOutput;
+ }
+
+ public void start() throws IOException {
+ if (isStarted()) {
+ throw new IllegalStateException("Already started!");
+ }
+
+ Log.verbose("executing " + this);
+
+ ProcessBuilder processBuilder = new ProcessBuilder()
+ .command(args)
+ .redirectErrorStream(true);
+ if (workingDirectory != null) {
+ processBuilder.directory(workingDirectory);
+ }
+
+ processBuilder.environment().putAll(env);
+
+ process = processBuilder.start();
+ }
+
+ public boolean isStarted() {
+ return process != null;
+ }
+
+ public InputStream getInputStream() {
+ if (!isStarted()) {
+ throw new IllegalStateException("Not started!");
+ }
+
+ return process.getInputStream();
+ }
+
+ public List<String> gatherOutput()
+ throws IOException, InterruptedException {
+ if (!isStarted()) {
+ throw new IllegalStateException("Not started!");
+ }
+
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(getInputStream(), "UTF-8"));
+ List<String> outputLines = new ArrayList<String>();
+ String outputLine;
+ while ((outputLine = in.readLine()) != null) {
+ if (tee != null) {
+ tee.println(outputLine);
+ }
+ if (nativeOutput) {
+ Log.nativeOutput(outputLine);
+ }
+ outputLines.add(outputLine);
+ }
+
+ if (process.waitFor() != 0 && !permitNonZeroExitStatus) {
+ StringBuilder message = new StringBuilder();
+ for (String line : outputLines) {
+ message.append("\n").append(line);
+ }
+ throw new CommandFailedException(args, outputLines);
+ }
+
+ return outputLines;
+ }
+
+ public List<String> execute() {
+ try {
+ start();
+ return gatherOutput();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to execute process: " + args, e);
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted while executing process: " + args, e);
+ }
+ }
+
+ /**
+ * Executes a command with a specified timeout. If the process does not
+ * complete normally before the timeout has elapsed, it will be destroyed.
+ *
+ * @param timeoutSeconds how long to wait, or 0 to wait indefinitely
+ * @return the command's output, or null if the command timed out
+ */
+ public List<String> executeWithTimeout(int timeoutSeconds)
+ throws TimeoutException {
+ if (timeoutSeconds == 0) {
+ return execute();
+ }
+
+ try {
+ return executeLater().get(timeoutSeconds, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted while executing process: " + args, e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ } finally {
+ destroy();
+ }
+ }
+
+ /**
+ * Executes the command on a new background thread. This method returns
+ * immediately.
+ *
+ * @return a future to retrieve the command's output.
+ */
+ public Future<List<String>> executeLater() {
+ ExecutorService executor = Threads.fixedThreadsExecutor("command", 1);
+ Future<List<String>> result = executor.submit(new Callable<List<String>>() {
+ public List<String> call() throws Exception {
+ start();
+ return gatherOutput();
+ }
+ });
+ executor.shutdown();
+ return result;
+ }
+
+ /**
+ * Destroys the underlying process and closes its associated streams.
+ */
+ public void destroy() {
+ if (process == null) {
+ return;
+ }
+
+ process.destroy();
+ try {
+ process.waitFor();
+ int exitValue = process.exitValue();
+ Log.verbose("received exit value " + exitValue
+ + " from destroyed command " + this);
+ } catch (IllegalThreadStateException destroyUnsuccessful) {
+ Log.warn("couldn't destroy " + this);
+ } catch (InterruptedException e) {
+ Log.warn("couldn't destroy " + this);
+ }
+ }
+
+ @Override public String toString() {
+ String envString = !env.isEmpty() ? (Strings.join(env.entrySet(), " ") + " ") : "";
+ return envString + Strings.join(args, " ");
+ }
+
+ public static class Builder {
+ private final List<String> args = new ArrayList<String>();
+ private final Map<String, String> env = new LinkedHashMap<String, String>();
+ private File workingDirectory;
+ private boolean permitNonZeroExitStatus = false;
+ private PrintStream tee = null;
+ private boolean nativeOutput;
+ private int maxLength = -1;
+
+ public Builder args(Object... objects) {
+ for (Object object : objects) {
+ args(object.toString());
+ }
+ return this;
+ }
+
+ public Builder setNativeOutput(boolean nativeOutput) {
+ this.nativeOutput = nativeOutput;
+ return this;
+ }
+
+ public Builder args(String... args) {
+ return args(Arrays.asList(args));
+ }
+
+ public Builder args(Collection<String> args) {
+ this.args.addAll(args);
+ return this;
+ }
+
+ public Builder env(String key, String value) {
+ env.put(key, value);
+ return this;
+ }
+
+ /**
+ * Sets the working directory from which the command will be executed.
+ * This must be a <strong>local</strong> directory; Commands run on
+ * remote devices (ie. via {@code adb shell}) require a local working
+ * directory.
+ */
+ public Builder workingDirectory(File workingDirectory) {
+ this.workingDirectory = workingDirectory;
+ return this;
+ }
+
+ public Builder tee(PrintStream printStream) {
+ tee = printStream;
+ return this;
+ }
+
+ public Builder maxLength(int maxLength) {
+ this.maxLength = maxLength;
+ return this;
+ }
+
+ public Command build() {
+ return new Command(this);
+ }
+
+ public List<String> execute() {
+ return build().execute();
+ }
+ }
+}
diff --git a/libs/vogar-expect/src/vogar/commands/CommandFailedException.java b/libs/vogar-expect/src/vogar/commands/CommandFailedException.java
new file mode 100644
index 0000000..3e08c11
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/commands/CommandFailedException.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2009 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 vogar.commands;
+
+import java.util.List;
+
+/**
+ * Thrown when an out of process executable does not return normally.
+ */
+public class CommandFailedException extends RuntimeException {
+
+ private final List<String> args;
+ private final List<String> outputLines;
+
+ public CommandFailedException(List<String> args, List<String> outputLines) {
+ super(formatMessage(args, outputLines));
+ this.args = args;
+ this.outputLines = outputLines;
+ }
+
+ public List<String> getArgs() {
+ return args;
+ }
+
+ public List<String> getOutputLines() {
+ return outputLines;
+ }
+
+ public static String formatMessage(List<String> args, List<String> outputLines) {
+ StringBuilder result = new StringBuilder();
+ result.append("Command failed:");
+ for (String arg : args) {
+ result.append(" ").append(arg);
+ }
+ for (String outputLine : outputLines) {
+ result.append("\n ").append(outputLine);
+ }
+ return result.toString();
+ }
+
+ private static final long serialVersionUID = 0;
+}
diff --git a/libs/vogar-expect/src/vogar/commands/Mkdir.java b/libs/vogar-expect/src/vogar/commands/Mkdir.java
new file mode 100644
index 0000000..fc08f1b
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/commands/Mkdir.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2010 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 vogar.commands;
+
+import java.io.File;
+
+/**
+ * A mkdir command.
+ */
+public final class Mkdir {
+
+ public void mkdirs(File directory) {
+ new Command("mkdir", "-p", directory.getPath()).execute();
+ }
+}
diff --git a/libs/vogar-expect/src/vogar/commands/Rm.java b/libs/vogar-expect/src/vogar/commands/Rm.java
new file mode 100644
index 0000000..5b39144
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/commands/Rm.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 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 vogar.commands;
+
+import java.io.File;
+
+/**
+ * A rm command.
+ */
+public final class Rm {
+
+ public void file(File file) {
+ new Command("rm", "-f", file.getPath()).execute();
+ }
+
+ public void directoryTree(File directory) {
+ new Command("rm", "-rf", directory.getPath()).execute();
+ }
+}
diff --git a/libs/vogar-expect/src/vogar/util/IoUtils.java b/libs/vogar-expect/src/vogar/util/IoUtils.java
new file mode 100644
index 0000000..4f1fba1
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/util/IoUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 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 vogar.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.Socket;
+
+public final class IoUtils {
+
+ public static void closeQuietly(Closeable c) {
+ if (c != null) {
+ try {
+ c.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ public static void closeQuietly(Socket c) {
+ if (c != null) {
+ try {
+ c.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+}
diff --git a/libs/vogar-expect/src/vogar/util/Log.java b/libs/vogar-expect/src/vogar/util/Log.java
new file mode 100644
index 0000000..99c0807
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/util/Log.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 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 vogar.util;
+
+import java.util.List;
+
+public class Log {
+
+ private static LogOutput sLogoutput = null;
+
+ public static void setOutput(LogOutput logOutput) {
+ sLogoutput = logOutput;
+ }
+
+ public static void verbose(String s) {
+ if (sLogoutput != null) {
+ sLogoutput.verbose(s);
+ }
+ }
+
+ public static void warn(String message) {
+ if (sLogoutput != null) {
+ sLogoutput.warn(message);
+ }
+ }
+
+ /**
+ * Warns, and also puts a list of strings afterwards.
+ */
+ public static void warn(String message, List<String> list) {
+ if (sLogoutput != null) {
+ sLogoutput.warn(message, list);
+ }
+ }
+
+ public static void info(String s) {
+ if (sLogoutput != null) {
+ sLogoutput.info(s);
+ }
+ }
+
+ public static void info(String message, Throwable throwable) {
+ if (sLogoutput != null) {
+ sLogoutput.info(message, throwable);
+ }
+ }
+
+ public static void nativeOutput(String outputLine) {
+ if (sLogoutput != null) {
+ sLogoutput.nativeOutput(outputLine);
+ }
+
+ }
+}
diff --git a/libs/vogar-expect/src/vogar/util/LogOutput.java b/libs/vogar-expect/src/vogar/util/LogOutput.java
new file mode 100644
index 0000000..8123a81
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/util/LogOutput.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 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 vogar.util;
+
+import java.util.List;
+
+public interface LogOutput {
+
+ void verbose(String s);
+
+ void warn(String message);
+
+ /**
+ * Warns, and also puts a list of strings afterwards.
+ */
+ void warn(String message, List<String> list);
+
+ void info(String s);
+
+ void info(String message, Throwable throwable);
+
+ void nativeOutput(String outputLine);
+
+}
diff --git a/libs/vogar-expect/src/vogar/util/MarkResetConsole.java b/libs/vogar-expect/src/vogar/util/MarkResetConsole.java
new file mode 100644
index 0000000..d88ce31
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/util/MarkResetConsole.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2010 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 vogar.util;
+
+import java.io.PrintStream;
+
+/**
+ * A console that can erase output back to a previously marked position.
+ */
+public final class MarkResetConsole {
+
+ private final PrintStream out;
+ private int row;
+ private final StringBuilder rowContent = new StringBuilder();
+
+ public MarkResetConsole(PrintStream out) {
+ this.out = out;
+ }
+
+ public void println(String text) {
+ print(text + "\n");
+ }
+
+ public void print(String text) {
+ for (int i = 0; i < text.length(); i++) {
+ if (text.charAt(i) == '\n') {
+ row++;
+ rowContent.delete(0, rowContent.length());
+ } else {
+ rowContent.append(text.charAt(i));
+ }
+ }
+
+ out.print(text);
+ out.flush();
+ }
+
+ public Mark mark() {
+ return new Mark();
+ }
+
+ public class Mark {
+ private final int markRow = row;
+ private final String markRowContent = rowContent.toString();
+
+ private Mark() {}
+
+ public void reset() {
+ /*
+ * ANSI escapes
+ * http://en.wikipedia.org/wiki/ANSI_escape_code
+ *
+ * \u001b[K clear the rest of the current line
+ * \u001b[nA move the cursor up n lines
+ * \u001b[nB move the cursor down n lines
+ * \u001b[nC move the cursor right n lines
+ * \u001b[nD move the cursor left n columns
+ */
+
+ for (int r = row; r > markRow; r--) {
+ // clear the line, up a line
+ System.out.print("\u001b[0G\u001b[K\u001b[1A");
+ }
+
+ // clear the line, reprint the line
+ out.print("\u001b[0G\u001b[K");
+ out.print(markRowContent);
+ rowContent.delete(0, rowContent.length());
+ rowContent.append(markRowContent);
+ row = markRow;
+ }
+ }
+}
diff --git a/libs/vogar-expect/src/vogar/util/Strings.java b/libs/vogar-expect/src/vogar/util/Strings.java
new file mode 100644
index 0000000..f92edd8
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/util/Strings.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2009 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 vogar.util;
+
+//import com.google.common.collect.Lists;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility methods for strings.
+ */
+public class Strings {
+
+ private static final Pattern XML_INVALID_CHARS
+ = Pattern.compile("[^\\u0009\\u000A\\u000D\\u0020-\\uD7FF\\uE000-\\uFFFD]+");
+
+ public static String readStream(Reader reader) throws IOException {
+ StringBuilder result = new StringBuilder();
+ BufferedReader in = new BufferedReader(reader);
+ String line;
+ while ((line = in.readLine()) != null) {
+ result.append(line);
+ result.append('\n');
+ }
+ in.close();
+ return result.toString();
+ }
+
+ public static String readFile(File f) throws IOException {
+ return readStream(new InputStreamReader(new FileInputStream(f), "UTF-8"));
+ }
+
+ public static List<String> readFileLines(File f) throws IOException {
+ BufferedReader in =
+ new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8"));
+ List<String> list = new ArrayList<String>();
+ String line;
+ while ((line = in.readLine()) != null) {
+ list.add(line);
+ }
+ in.close();
+ return list;
+ }
+
+ public static String join(String delimiter, Object... objects) {
+ return join(Arrays.asList(objects), delimiter);
+ }
+
+ public static String join(Iterable<?> objects, String delimiter) {
+ Iterator<?> i = objects.iterator();
+ if (!i.hasNext()) {
+ return "";
+ }
+
+ StringBuilder result = new StringBuilder();
+ result.append(i.next());
+ while(i.hasNext()) {
+ result.append(delimiter).append(i.next());
+ }
+ return result.toString();
+ }
+
+ public static String[] objectsToStrings(Object[] objects) {
+ String[] result = new String[objects.length];
+ int i = 0;
+ for (Object o : objects) {
+ result[i++] = o.toString();
+ }
+ return result;
+ }
+
+ public static String[] objectsToStrings(Collection<?> objects) {
+ return objectsToStrings(objects.toArray());
+ }
+
+ /**
+ * Replaces XML-invalid characters with the corresponding U+XXXX code point escapes.
+ */
+ public static String xmlSanitize(String text) {
+ StringBuffer result = new StringBuffer();
+ Matcher matcher = XML_INVALID_CHARS.matcher(text);
+ while (matcher.find()) {
+ matcher.appendReplacement(result, "");
+ result.append(escapeCodePoint(matcher.group()));
+ }
+ matcher.appendTail(result);
+ return result.toString();
+ }
+
+ private static String escapeCodePoint(CharSequence cs) {
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < cs.length(); ++i) {
+ result.append(String.format("U+%04X", (int) cs.charAt(i)));
+ }
+ return result.toString();
+ }
+}
diff --git a/libs/vogar-expect/src/vogar/util/Threads.java b/libs/vogar-expect/src/vogar/util/Threads.java
new file mode 100644
index 0000000..83410d5
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/util/Threads.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2009 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 vogar.util;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utility methods for working with threads.
+ */
+public final class Threads {
+ private Threads() {}
+
+ public static ThreadFactory daemonThreadFactory(final String name) {
+ return new ThreadFactory() {
+ private int nextId = 0;
+ public synchronized Thread newThread(Runnable r) {
+ Thread thread = new Thread(r, name + "-" + (nextId++));
+ thread.setDaemon(true);
+ return thread;
+ }
+ };
+ }
+
+ public static ExecutorService threadPerCpuExecutor(String name) {
+ return fixedThreadsExecutor(name, Runtime.getRuntime().availableProcessors());
+ }
+
+ public static ExecutorService fixedThreadsExecutor(String name, int count) {
+ ThreadFactory threadFactory = daemonThreadFactory(name);
+
+ return new ThreadPoolExecutor(count, count, 10, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<Runnable>(Integer.MAX_VALUE), threadFactory) {
+ @Override protected void afterExecute(Runnable runnable, Throwable throwable) { if (throwable != null) {
+ Log.info("Unexpected failure from " + runnable, throwable);
+ }
+ }
+ };
+ }
+}
diff --git a/libs/vogar-expect/src/vogar/util/TimeUtilities.java b/libs/vogar-expect/src/vogar/util/TimeUtilities.java
new file mode 100644
index 0000000..c5a7e3b
--- /dev/null
+++ b/libs/vogar-expect/src/vogar/util/TimeUtilities.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2010 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 vogar.util;
+
+/**
+ * Utilities to make it easier to work with ISO 8601 dates and times.
+ * This is a subset of the original class from http://software.jessies.org/salma-hayek/ --- please submit fixes upstream.
+ */
+public class TimeUtilities {
+ /**
+ * Returns the ISO 8601-format String corresponding to the given duration (measured in milliseconds).
+ */
+ public static String msToIsoString(long duration) {
+ long milliseconds = duration % 1000;
+ duration /= 1000;
+ long seconds = duration % 60;
+ duration /= 60;
+ long minutes = duration % 60;
+ duration /= 60;
+ long hours = duration;
+
+ StringBuilder result = new StringBuilder("P");
+ if (hours != 0) {
+ result.append(hours);
+ result.append('H');
+ }
+ if (result.length() > 1 || minutes != 0) {
+ result.append(minutes);
+ result.append('M');
+ }
+ result.append(seconds);
+ if (milliseconds != 0) {
+ result.append('.');
+ result.append(milliseconds);
+ }
+ result.append('S');
+ return result.toString();
+ }
+
+ /**
+ * Returns a string representation of the given number of milliseconds.
+ */
+ public static String msToString(long ms) {
+ return nsToString(ms * 1000000);
+ }
+
+ /**
+ * Returns a string representation of the given number of nanoseconds.
+ */
+ public static String nsToString(long ns) {
+ if (ns < 1000L) {
+ return Long.toString(ns) + "ns";
+ } else if (ns < 1000000L) {
+ return Long.toString(ns/1000L) + "us";
+ } else if (ns < 1000000000L) {
+ return Long.toString(ns/1000000L) + "ms";
+ } else if (ns < 60000000000L) {
+ return String.format("%.2fs", nsToS(ns));
+ } else {
+ long duration = ns;
+ long nanoseconds = duration % 1000;
+ duration /= 1000;
+ long microseconds = duration % 1000;
+ duration /= 1000;
+ long milliseconds = duration % 1000;
+ duration /= 1000;
+ long seconds = duration % 60;
+ duration /= 60;
+ long minutes = duration % 60;
+ duration /= 60;
+ long hours = duration % 24;
+ duration /= 24;
+ long days = duration;
+
+ StringBuilder result = new StringBuilder();
+ if (days != 0) {
+ result.append(days);
+ result.append('d');
+ }
+ if (result.length() > 1 || hours != 0) {
+ result.append(hours);
+ result.append('h');
+ }
+ if (result.length() > 1 || minutes != 0) {
+ result.append(minutes);
+ result.append('m');
+ }
+ result.append(seconds);
+ result.append('s');
+ return result.toString();
+ }
+ }
+
+ /**
+ * Converts nanoseconds into (fractional) seconds.
+ */
+ public static double nsToS(long ns) {
+ return ((double) ns)/1000000000.0;
+ }
+
+ private TimeUtilities() {
+ }
+}
diff --git a/tests/res/layout/textview_layout.xml b/tests/res/layout/textview_layout.xml
index 5689bc5..d0ed1e0 100644
--- a/tests/res/layout/textview_layout.xml
+++ b/tests/res/layout/textview_layout.xml
@@ -15,43 +15,39 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <TextView android:id="@+id/textview_textAttr"
- android:text="@string/text_view_hello"
- android:textColor="@drawable/black"
- android:textColorHighlight="@drawable/yellow"
- android:textColorHint="@drawable/red"
- android:textColorLink="@drawable/blue"
- android:textScaleX="1.2"
- android:textSize="20px"
- android:textStyle="normal"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <TextView android:id="@+id/textview_password"
- android:password="true"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <TextView android:id="@+id/textview_singleLine"
- android:singleLine="true"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <TextView android:id="@+id/textview_compound"
- android:drawablePadding="2px"
- android:drawableLeft="@drawable/red"
- android:drawableTop="@drawable/yellow"
- android:drawableRight="@drawable/blue"
- android:drawableBottom="@drawable/black"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <TextView android:id="@+id/textview_text"
- android:text="@string/text_view_hello"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ScrollView android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView android:id="@+id/textview_textAttr"
+ android:text="@string/text_view_hello"
+ android:textColor="@drawable/black"
+ android:textColorHighlight="@drawable/yellow"
+ android:textColorHint="@drawable/red"
+ android:textColorLink="@drawable/blue"
+ android:textScaleX="1.2"
+ android:textSize="20px"
+ android:textStyle="normal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView android:id="@+id/textview_password"
+ android:password="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView android:id="@+id/textview_singleLine"
+ android:singleLine="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView android:id="@+id/textview_text"
+ android:text="@string/text_view_hello"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+ </ScrollView>
</LinearLayout>
diff --git a/tests/src/android/text/format/cts/LocaleUtils.java b/tests/src/android/text/format/cts/LocaleUtils.java
index 789ad74..d6001c4 100644
--- a/tests/src/android/text/format/cts/LocaleUtils.java
+++ b/tests/src/android/text/format/cts/LocaleUtils.java
@@ -16,15 +16,17 @@
package android.text.format.cts;
+import android.content.Context;
+
import java.util.Locale;
public class LocaleUtils {
/** Return whether or not the specified locale is available on the system. */
- public static boolean isSupportedLocale(Locale locale) {
- Locale[] locales = Locale.getAvailableLocales();
- for (Locale availableLocale : locales) {
- if (locale.equals(availableLocale)) {
+ public static boolean isSupportedLocale(Context context, Locale locale) {
+ String[] locales = context.getAssets().getLocales();
+ for (String availableLocale : locales) {
+ if (locale.toString().equals(availableLocale)) {
return true;
}
}
diff --git a/tests/src/android/webkit/cts/CtsTestServer.java b/tests/src/android/webkit/cts/CtsTestServer.java
index 25c282f..91f4f1e 100644
--- a/tests/src/android/webkit/cts/CtsTestServer.java
+++ b/tests/src/android/webkit/cts/CtsTestServer.java
@@ -403,7 +403,10 @@
if (path.startsWith(AUTH_PREFIX)) {
// authentication required
Header[] auth = request.getHeaders("Authorization");
- if (auth.length > 0 && auth[0].getValue().equals(AUTH_CREDENTIALS)) {
+ if ((auth.length > 0 && auth[0].getValue().equals(AUTH_CREDENTIALS))
+ // This is a hack to make sure that loads to this url's will always
+ // ask for authentication. This is what the test expects.
+ && !path.endsWith("embedded_image.html")) {
// fall through and serve content
path = path.substring(AUTH_PREFIX.length());
} else {
diff --git a/tests/tests/app/src/android/app/cts/DatePickerDialogTest.java b/tests/tests/app/src/android/app/cts/DatePickerDialogTest.java
deleted file mode 100644
index 4ca5a4e..0000000
--- a/tests/tests/app/src/android/app/cts/DatePickerDialogTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2008 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.app.cts;
-
-import dalvik.annotation.BrokenTest;
-import dalvik.annotation.TestLevel;
-import dalvik.annotation.TestTargetClass;
-import dalvik.annotation.TestTargetNew;
-import dalvik.annotation.TestTargets;
-
-import android.app.DatePickerDialog;
-import android.app.Instrumentation;
-import android.app.DatePickerDialog.OnDateSetListener;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.test.ActivityInstrumentationTestCase2;
-import android.text.TextUtils.TruncateAt;
-import android.view.KeyEvent;
-import android.widget.DatePicker;
-import android.widget.TextView;
-
-@TestTargetClass(DatePickerDialog.class)
-public class DatePickerDialogTest extends ActivityInstrumentationTestCase2<DialogStubActivity> {
-
- private Instrumentation mInstrumentation;
- private DialogStubActivity mActivity;
-
- public DatePickerDialogTest() {
- super("com.android.cts.stub", DialogStubActivity.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mInstrumentation = getInstrumentation();
- }
-
- @TestTargets({
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "DatePickerDialog",
- args = {Context.class, int.class, OnDateSetListener.class, int.class, int.class,
- int.class}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "onSaveInstanceState",
- args = {}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "onClick",
- args = {DialogInterface.class, int.class}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "onDateChanged",
- args = {DatePicker.class, int.class, int.class, int.class}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "onRestoreInstanceState",
- args = {android.os.Bundle.class}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "show",
- args = {}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "updateDate",
- args = {int.class, int.class, int.class}
- )
- })
- @BrokenTest("assume layout of DatePickerDialog")
- public void testDatePickerDialogWithTheme() throws Exception {
- doTestDatePickerDialog(DialogStubActivity.TEST_DATEPICKERDIALOG_WITH_THEME);
- }
-
- @TestTargets({
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "DatePickerDialog",
- args = {Context.class, OnDateSetListener.class, int.class, int.class, int.class}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "onSaveInstanceState",
- args = {}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "onClick",
- args = {DialogInterface.class, int.class}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "onDateChanged",
- args = {DatePicker.class, int.class, int.class, int.class}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "onRestoreInstanceState",
- args = {android.os.Bundle.class}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "show",
- args = {}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "updateDate",
- args = {int.class, int.class, int.class}
- )
- })
- @BrokenTest("assume layout of DatePickerDialog")
- public void testDatePickerDialog() throws Exception {
- doTestDatePickerDialog(DialogStubActivity.TEST_DATEPICKERDIALOG);
- }
-
- private void doTestDatePickerDialog(int index) throws Exception {
- startDialogActivity(index);
- final DatePickerDialog datePickerDialog = (DatePickerDialog) mActivity.getDialog();
- assertTrue(datePickerDialog.isShowing());
- final TextView title = (TextView) datePickerDialog.findViewById(
- com.android.internal.R.id.alertTitle);
- assertEquals(TruncateAt.END, title.getEllipsize());
-
- // move the focus to the 'set' button
- mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
- // move the focus up to the '-' button under the month
- mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
- // decrement the month (moves focus to date field)
- mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ENTER);
- // move focus down to '-' button under the month
- mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
- // move focus down to 'set' button
- mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
- // click the 'set' button to accept changes
- mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ENTER);
-
- mInstrumentation.waitForIdleSync();
- assertTrue(mActivity.onClickCalled);
- assertEquals(mActivity.updatedYear, mActivity.INITIAL_YEAR);
- assertEquals(mActivity.updatedMonth + 1, mActivity.INITIAL_MONTH);
- assertEquals(mActivity.updatedDay, mActivity.INITIAL_DAY_OF_MONTH);
- assertTrue(DialogStubActivity.onDateChangedCalled);
-
- assertFalse(mActivity.onSaveInstanceStateCalled);
- assertFalse(DialogStubActivity.onRestoreInstanceStateCalled);
- OrientationTestUtils.toggleOrientationSync(mActivity, mInstrumentation);
- assertTrue(mActivity.onSaveInstanceStateCalled);
- assertTrue(DialogStubActivity.onRestoreInstanceStateCalled);
- }
-
- private void startDialogActivity(int index) {
- mActivity = DialogStubActivity.startDialogActivity(this, index);
- }
-}
diff --git a/tests/tests/app/src/android/app/cts/DialogTest.java b/tests/tests/app/src/android/app/cts/DialogTest.java
index 3500d27..5ac3ec7 100644
--- a/tests/tests/app/src/android/app/cts/DialogTest.java
+++ b/tests/tests/app/src/android/app/cts/DialogTest.java
@@ -40,6 +40,7 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.SystemClock;
import android.test.ActivityInstrumentationTestCase2;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -57,8 +58,6 @@
public class DialogTest extends ActivityInstrumentationTestCase2<DialogStubActivity> {
protected static final long SLEEP_TIME = 200;
- private static final long MOTION_DOWN_TIME = 0L;
- private static final long MOTION_EVENT_TIME = 0L;
private static final float MOTION_X = -20.0f;
private static final float MOTION_Y = -20.0f;
private static final String STUB_ACTIVITY_PACKAGE = "com.android.cts.stub";
@@ -545,8 +544,9 @@
assertNull(d.touchEvent);
assertFalse(d.isOnTouchEventCalled);
- MotionEvent touchMotionEvent = MotionEvent.obtain(MOTION_DOWN_TIME,
- MOTION_EVENT_TIME, MotionEvent.ACTION_DOWN,
+ long eventTime = SystemClock.uptimeMillis();
+ MotionEvent touchMotionEvent = MotionEvent.obtain(eventTime,
+ eventTime, MotionEvent.ACTION_DOWN,
MOTION_X, MOTION_Y, 0);
// send a touch motion event, and System will call onTouchEvent
mInstrumentation.sendPointerSync(touchMotionEvent);
@@ -561,8 +561,8 @@
// set cancel on touch out side
d.setCanceledOnTouchOutside(true);
- touchMotionEvent = MotionEvent.obtain(MOTION_DOWN_TIME + 1,
- MOTION_EVENT_TIME, MotionEvent.ACTION_DOWN,
+ touchMotionEvent = MotionEvent.obtain(eventTime + 1,
+ eventTime, MotionEvent.ACTION_DOWN,
MOTION_X, MOTION_Y, 0);
// send a out side touch motion event, then the dialog will dismiss
mInstrumentation.sendPointerSync(touchMotionEvent);
@@ -590,7 +590,8 @@
public void testTrackballEvent() {
startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
final TestDialog d = (TestDialog) mActivity.getDialog();
- final MotionEvent trackBallEvent = MotionEvent.obtain(MOTION_DOWN_TIME, MOTION_EVENT_TIME,
+ long eventTime = SystemClock.uptimeMillis();
+ final MotionEvent trackBallEvent = MotionEvent.obtain(eventTime, eventTime,
MotionEvent.ACTION_DOWN, MOTION_X, MOTION_Y, 0);
assertNull(d.trackballEvent);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
index 6ca1806..32dc5ca 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/InsetDrawableTest.java
@@ -18,7 +18,6 @@
import com.android.cts.stub.R;
-import dalvik.annotation.BrokenTest;
import dalvik.annotation.TestLevel;
import dalvik.annotation.TestTargetClass;
import dalvik.annotation.TestTargetNew;
@@ -305,15 +304,14 @@
method = "getOpacity",
args = {}
)
- @BrokenTest(value="bug 2397630 - needs investigation")
public void testGetOpacity() {
- Drawable d = mContext.getResources().getDrawable(R.drawable.pass);
+ Drawable d = mContext.getResources().getDrawable(R.drawable.testimage);
InsetDrawable insetDrawable = new InsetDrawable(d, 0);
+ insetDrawable.setAlpha(255);
assertEquals(PixelFormat.OPAQUE, insetDrawable.getOpacity());
- d = mContext.getResources().getDrawable(R.drawable.testimage);
- insetDrawable = new InsetDrawable(d, 0);
- assertEquals(PixelFormat.OPAQUE, insetDrawable.getOpacity());
+ insetDrawable.setAlpha(100);
+ assertEquals(PixelFormat.TRANSLUCENT, insetDrawable.getOpacity());
}
@TestTargetNew(
@@ -443,22 +441,6 @@
assertNotNull(constantState);
}
- @TestTargetNew(
- level = TestLevel.SUFFICIENT,
- method = "mutate",
- args = {}
- )
- public void testMutate() {
- Resources resources = mContext.getResources();
- InsetDrawable d1 = (InsetDrawable) resources.getDrawable(R.drawable.insetdrawable);
-
- d1.setAlpha(100);
- d1.mutate();
- d1.setAlpha(200);
-
- // Cannot test whether alpha was set properly.
- }
-
private class MockInsetDrawable extends InsetDrawable {
public MockInsetDrawable(Drawable drawable, int inset) {
super(drawable, inset);
diff --git a/tests/tests/graphics/src/android/opengl/cts/OpenGlEsVersionTest.java b/tests/tests/graphics/src/android/opengl/cts/OpenGlEsVersionTest.java
index f1acd87..3ebc567 100644
--- a/tests/tests/graphics/src/android/opengl/cts/OpenGlEsVersionTest.java
+++ b/tests/tests/graphics/src/android/opengl/cts/OpenGlEsVersionTest.java
@@ -83,33 +83,42 @@
EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
int[] numConfigs = new int[1];
- if (egl.eglGetConfigs(display, null, 0, numConfigs)) {
- EGLConfig[] configs = new EGLConfig[numConfigs[0]];
- if (egl.eglGetConfigs(display, configs, numConfigs[0], numConfigs)) {
- int[] value = new int[1];
- for (int i = 0; i < numConfigs[0]; i++) {
- if (egl.eglGetConfigAttrib(display, configs[i],
- EGL10.EGL_RENDERABLE_TYPE, value)) {
- if ((value[0] & EGL_OPENGL_ES2_BIT) == EGL_OPENGL_ES2_BIT) {
- return 2;
+ if (egl.eglInitialize(display, null)) {
+ try {
+ if (egl.eglGetConfigs(display, null, 0, numConfigs)) {
+ EGLConfig[] configs = new EGLConfig[numConfigs[0]];
+ if (egl.eglGetConfigs(display, configs, numConfigs[0], numConfigs)) {
+ int[] value = new int[1];
+ for (int i = 0; i < numConfigs[0]; i++) {
+ if (egl.eglGetConfigAttrib(display, configs[i],
+ EGL10.EGL_RENDERABLE_TYPE, value)) {
+ if ((value[0] & EGL_OPENGL_ES2_BIT) == EGL_OPENGL_ES2_BIT) {
+ return 2;
+ }
+ } else {
+ Log.w(TAG, "Getting config attribute with "
+ + "EGL10#eglGetConfigAttrib failed "
+ + "(" + i + "/" + numConfigs[0] + "): "
+ + egl.eglGetError());
+ }
}
+ return 1;
} else {
- Log.w(TAG, "Getting config attribute with "
- + "EGL10#eglGetConfigAttrib failed "
- + "(" + i + "/" + numConfigs[0] + "): "
+ Log.e(TAG, "Getting configs with EGL10#eglGetConfigs failed: "
+ egl.eglGetError());
+ return -1;
}
+ } else {
+ Log.e(TAG, "Getting number of configs with EGL10#eglGetConfigs failed: "
+ + egl.eglGetError());
+ return -2;
}
- return 1;
- } else {
- Log.e(TAG, "Getting configs with EGL10#eglGetConfigs failed: "
- + egl.eglGetError());
- return -1;
- }
+ } finally {
+ egl.eglTerminate(display);
+ }
} else {
- Log.e(TAG, "Getting number of configs with EGL10#eglGetConfigs failed: "
- + egl.eglGetError());
- return -2;
+ Log.e(TAG, "Couldn't initialize EGL.");
+ return -3;
}
}
diff --git a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
index 0706287..944de6c 100644
--- a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
@@ -849,25 +849,10 @@
assertEquals(focalLength, exifFocalLength, 0.001);
// Test gps exif tags.
- mCamera.startPreview();
- parameters.setGpsLatitude(37.736071);
- parameters.setGpsLongitude(-122.441983);
- parameters.setGpsAltitude(21);
- parameters.setGpsTimestamp(1199145600);
- String thirtyTwoCharacters = "GPS NETWORK HYBRID ARE ALL FINE.";
- parameters.setGpsProcessingMethod(thirtyTwoCharacters);
- mCamera.setParameters(parameters);
- mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
- waitForSnapshotDone();
- exif = new ExifInterface(JPEG_PATH);
- assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
- assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE));
- assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF));
- assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF));
- assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
- assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_DATESTAMP));
- assertEquals(thirtyTwoCharacters,
- exif.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD));
+ testGpsExifValues(parameters, 37.736071, -122.441983, 21, 1199145600,
+ "GPS NETWORK HYBRID ARE ALL FINE.");
+ testGpsExifValues(parameters, 0.736071, 0.441983, 1, 1199145601, "GPS");
+ testGpsExifValues(parameters, -89.736071, -179.441983, 100000, 1199145602, "NETWORK");
// Test gps tags do not exist after calling removeGpsData.
mCamera.startPreview();
@@ -880,6 +865,33 @@
terminateMessageLooper();
}
+ private void testGpsExifValues(Parameters parameters, double latitude,
+ double longitude, double altitude, long timestamp, String method)
+ throws IOException {
+ mCamera.startPreview();
+ parameters.setGpsLatitude(latitude);
+ parameters.setGpsLongitude(longitude);
+ parameters.setGpsAltitude(altitude);
+ parameters.setGpsTimestamp(timestamp);
+ parameters.setGpsProcessingMethod(method);
+ mCamera.setParameters(parameters);
+ mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
+ waitForSnapshotDone();
+ ExifInterface exif = new ExifInterface(JPEG_PATH);
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE));
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF));
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF));
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
+ assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_DATESTAMP));
+ assertEquals(method, exif.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD));
+ float[] latLong = new float[2];
+ assertTrue(exif.getLatLong(latLong));
+ assertEquals((float)latitude, latLong[0], 0.0001f);
+ assertEquals((float)longitude, latLong[1], 0.0001f);
+ assertEquals(altitude, exif.getAltitude(-1), 1);
+ }
+
private void checkGpsDataNull(ExifInterface exif) {
assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE));
diff --git a/tests/tests/media/src/android/media/cts/AudioEffectTest.java b/tests/tests/media/src/android/media/cts/AudioEffectTest.java
index 514f6a7..0aaf11f 100644
--- a/tests/tests/media/src/android/media/cts/AudioEffectTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioEffectTest.java
@@ -137,23 +137,25 @@
AudioEffect.Descriptor[] desc = AudioEffect.queryEffects();
assertTrue("no effects found", (desc.length != 0));
for (int i = 0; i < desc.length; i++) {
- try {
- AudioEffect effect = new AudioEffect(desc[i].type,
- AudioEffect.EFFECT_TYPE_NULL,
- 0,
- 0);
- assertNotNull("could not create AudioEffect", effect);
+ if (!desc[i].type.equals(AudioEffect.EFFECT_TYPE_NULL)) {
try {
- assertTrue("invalid effect ID", (effect.getId() != 0));
- } catch (IllegalStateException e) {
- fail("AudioEffect not initialized");
- } finally {
- effect.release();
+ AudioEffect effect = new AudioEffect(desc[i].type,
+ AudioEffect.EFFECT_TYPE_NULL,
+ 0,
+ 0);
+ assertNotNull("could not create AudioEffect", effect);
+ try {
+ assertTrue("invalid effect ID", (effect.getId() != 0));
+ } catch (IllegalStateException e) {
+ fail("AudioEffect not initialized");
+ } finally {
+ effect.release();
+ }
+ } catch (IllegalArgumentException e) {
+ fail("Effect not found: "+desc[i].name);
+ } catch (UnsupportedOperationException e) {
+ fail("Effect library not loaded");
}
- } catch (IllegalArgumentException e) {
- fail("Effect not found: "+desc[i].name);
- } catch (UnsupportedOperationException e) {
- fail("Effect library not loaded");
}
}
}
@@ -1376,4 +1378,4 @@
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index 635c196..46e3287 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -28,9 +28,6 @@
import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
-import static android.media.AudioManager.ROUTE_BLUETOOTH_SCO;
-import static android.media.AudioManager.ROUTE_EARPIECE;
-import static android.media.AudioManager.ROUTE_SPEAKER;
import static android.media.AudioManager.STREAM_MUSIC;
import static android.media.AudioManager.USE_DEFAULT_STREAM_TYPE;
import static android.media.AudioManager.VIBRATE_SETTING_OFF;
@@ -586,12 +583,20 @@
mAudioManager.setStreamVolume(streams[i], 1, FLAG_SHOW_UI);
mAudioManager.adjustStreamVolume(streams[i], ADJUST_LOWER, FLAG_ALLOW_RINGER_MODES);
// lowering the volume should have changed the ringer mode
- assertEquals(RINGER_MODE_VIBRATE, mAudioManager.getRingerMode());
+ assertTrue(mAudioManager.getRingerMode() == RINGER_MODE_VIBRATE ||
+ mAudioManager.getRingerMode() == RINGER_MODE_SILENT);
mAudioManager.adjustStreamVolume(streams[i], ADJUST_LOWER, FLAG_ALLOW_RINGER_MODES);
// adjusting the volume to zero should result in either silent or vibrate mode
assertTrue(mAudioManager.getRingerMode() == RINGER_MODE_VIBRATE ||
mAudioManager.getRingerMode() == RINGER_MODE_SILENT);
mAudioManager.adjustStreamVolume(streams[i], ADJUST_RAISE, FLAG_ALLOW_RINGER_MODES);
+ // There are two possible ways the device may work. It may have a silent/vibrate
+ // mode or it may have distinct silent and vibrate modes.
+ assertTrue(mAudioManager.getRingerMode() == RINGER_MODE_NORMAL ||
+ mAudioManager.getRingerMode() == RINGER_MODE_VIBRATE);
+ // Increase the volume one more time to get out of the vibrate mode which may
+ // be separate from silent mode.
+ mAudioManager.adjustStreamVolume(streams[i], ADJUST_RAISE, FLAG_ALLOW_RINGER_MODES);
assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
}
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index cfe0872..17a3ac1 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -143,8 +143,8 @@
NetworkInfo[] ni = mCm.getAllNetworkInfo();
for (NetworkInfo n : ni) {
- // make sure network is up
- if (n.isConnected()) {
+ // make sure network is up (except WIFI due to always fail)
+ if (n.isConnected() && (n.getType() != TYPE_WIFI)) {
assertTrue(mCm.requestRouteToHost(n.getType(), HOST_ADDRESS));
}
}
diff --git a/tests/tests/net/src/android/net/cts/TrafficStatsTest.java b/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
index 9d23a87..183f891 100644
--- a/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
+++ b/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
@@ -16,24 +16,20 @@
package android.net.cts;
-import android.os.Process;
-import android.net.TrafficStats;
-import android.test.AndroidTestCase;
-
-import dalvik.annotation.TestTargets;
import dalvik.annotation.TestLevel;
-import dalvik.annotation.TestTargetNew;
import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.TestTargets;
+
+import android.net.TrafficStats;
+import android.os.Process;
+import android.test.AndroidTestCase;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
-import java.net.UnknownHostException;
-import java.util.Random;
@TestTargetClass(TrafficStats.class)
public class TrafficStatsTest extends AndroidTestCase {
@@ -58,63 +54,6 @@
}
@TestTargets({
- @TestTargetNew(level = TestLevel.PARTIAL_COMPLETE, method = "getTotalTxPackets"),
- @TestTargetNew(level = TestLevel.PARTIAL_COMPLETE, method = "getTotalRxPackets"),
- @TestTargetNew(level = TestLevel.PARTIAL_COMPLETE, method = "getTotalTxBytes"),
- @TestTargetNew(level = TestLevel.PARTIAL_COMPLETE, method = "getTotalRxBytes"),
- @TestTargetNew(level = TestLevel.PARTIAL_COMPLETE, method = "getUidTxBytes"),
- @TestTargetNew(level = TestLevel.PARTIAL_COMPLETE, method = "getUidRxBytes")
- })
- public void testTrafficStatsWithHostLookup() {
- long txPacketsBefore = TrafficStats.getTotalTxPackets();
- long rxPacketsBefore = TrafficStats.getTotalRxPackets();
- long txBytesBefore = TrafficStats.getTotalTxBytes();
- long rxBytesBefore = TrafficStats.getTotalRxBytes();
- long uidTxBytesBefore = TrafficStats.getUidTxBytes(Process.myUid());
- long uidRxBytesBefore = TrafficStats.getUidRxBytes(Process.myUid());
-
- // Look up random hostnames in a wildcard domain owned by Google.
- // This will require a DNS request, which should generate traffic.
-
- int found = 0;
- Random r = new Random();
- for (int i = 0; i < 10; i++) {
- try {
- String host = "test" + r.nextInt(100000) + ".clients.google.com";
- InetAddress[] addr = InetAddress.getAllByName(host);
- if (addr.length > 0) found++;
- } catch (UnknownHostException e) {
- // Ignore -- our purpose is not to test network connectivity,
- // and we'd rather have false positives than a flaky test.
- }
- }
-
- long txPacketsAfter = TrafficStats.getTotalTxPackets();
- long rxPacketsAfter = TrafficStats.getTotalRxPackets();
- long txBytesAfter = TrafficStats.getTotalTxBytes();
- long rxBytesAfter = TrafficStats.getTotalRxBytes();
- long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
- long uidRxBytesAfter = TrafficStats.getUidRxBytes(Process.myUid());
-
- // Make some conservative assertions about the data used:
- // each successful resolution should exchange at least one packet,
- // and at least 20 bytes in each direction.
-
- assertTrue("txp: " + txPacketsBefore + " [" + found + "] " + txPacketsAfter,
- txPacketsAfter >= txPacketsBefore + found);
- assertTrue("rxp: " + rxPacketsBefore + " [" + found + "] " + rxPacketsAfter,
- rxPacketsAfter >= rxPacketsBefore + found);
- assertTrue("txb: " + txBytesBefore + " [" + found + "] " + txBytesAfter,
- txBytesAfter >= txBytesBefore + found * 20);
- assertTrue("rxb: " + rxBytesBefore + " [" + found + "] " + rxBytesAfter,
- rxBytesAfter >= rxBytesBefore + found * 20);
- assertTrue("uidtxb: " + uidTxBytesBefore + " [" + found + "] " + uidTxBytesAfter,
- uidTxBytesAfter >= uidTxBytesBefore + found * 20);
- assertTrue("uidrxb: " + uidRxBytesBefore + " [" + found + "] " + uidRxBytesAfter,
- uidRxBytesAfter >= uidRxBytesBefore + found * 20);
- }
-
- @TestTargets({
@TestTargetNew(level = TestLevel.PARTIAL_COMPLETE, method = "getMobileTxPackets"),
@TestTargetNew(level = TestLevel.PARTIAL_COMPLETE, method = "getMobileRxPackets"),
@TestTargetNew(level = TestLevel.PARTIAL_COMPLETE, method = "getMobileTxBytes"),
diff --git a/tests/tests/os/src/android/os/cts/BuildVersionTest.java b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
index 7225363..8855492 100644
--- a/tests/tests/os/src/android/os/cts/BuildVersionTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
@@ -21,18 +21,26 @@
import android.os.Build;
import android.util.Log;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
import junit.framework.TestCase;
@TestTargetClass(Build.VERSION.class)
public class BuildVersionTest extends TestCase {
private static final String LOG_TAG = "BuildVersionTest";
- private static final String EXPECTED_RELEASE = "2.3";
+ private static final Set<String> EXPECTED_RELEASES =
+ new HashSet<String>(Arrays.asList("2.3", "2.3.1", "2.3.2"));
private static final int EXPECTED_SDK = 9;
public void testReleaseVersion() {
// Applications may rely on the exact release version
- assertEquals(EXPECTED_RELEASE, Build.VERSION.RELEASE);
+ assertTrue("Your Build.VERSION.RELEASE of " + Build.VERSION.RELEASE
+ + " was not one of the following: " + EXPECTED_RELEASES,
+ EXPECTED_RELEASES.contains(Build.VERSION.RELEASE));
+
assertEquals("" + EXPECTED_SDK, Build.VERSION.SDK);
assertEquals(EXPECTED_SDK, Build.VERSION.SDK_INT);
}
diff --git a/tests/tests/os/src/android/os/cts/FileAccessPermissionTest.java b/tests/tests/os/src/android/os/cts/FileAccessPermissionTest.java
old mode 100644
new mode 100755
index ffed104..cd89e3d
--- a/tests/tests/os/src/android/os/cts/FileAccessPermissionTest.java
+++ b/tests/tests/os/src/android/os/cts/FileAccessPermissionTest.java
@@ -21,6 +21,7 @@
import java.io.File;
import java.io.FileInputStream;
+import java.io.FilenameFilter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -65,15 +66,33 @@
file = new File("/system/app");
assertTrue(file.canRead());
assertFalse(file.canWrite());
- File[] apkFiles = file.listFiles();
- for (File f : apkFiles) {
- assertTrue(f.canRead());
+
+ // Test not writable / deletable for all files
+ File[] allFiles = file.listFiles();
+ for (File f : allFiles) {
assertFalse(f.canWrite());
assertFalse(f.delete());
}
}
/**
+ * Test apks in /system/app.
+ */
+ public void testApksAlwaysReadable() {
+ File file = new File("/system/app");
+
+ // Test readable for only apk files
+ File[] apkFiles = file.listFiles(new FilenameFilter() {
+ public boolean accept(File dir, String filename) {
+ return filename.endsWith(".apk");
+ }
+ });
+ for (File f : apkFiles) {
+ assertTrue(f.canRead());
+ }
+ }
+
+ /**
* Test dir which app can and cannot access.
*/
public void testAccessAppDataDir() {
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index 7b3a78e..3cbb362 100644
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -200,6 +200,47 @@
}
}
+ public void testAllFilesInSysAreNotWritable() throws Exception {
+ assertAllFilesInDirAndSubDirAreNotWritable(new File("/sys"));
+ }
+
+ private static void
+ assertAllFilesInDirAndSubDirAreNotWritable(File dir) throws Exception {
+ assertTrue(dir.isDirectory());
+
+ if (isSymbolicLink(dir)) {
+ // don't examine symbolic links.
+ return;
+ }
+
+ File[] subDirectories = dir.listFiles(new FileFilter() {
+ @Override public boolean accept(File pathname) {
+ return pathname.isDirectory();
+ }
+ });
+
+
+ /* recurse into subdirectories */
+ if (subDirectories != null) {
+ for (File f : subDirectories) {
+ assertAllFilesInDirAndSubDirAreNotWritable(f);
+ }
+ }
+
+ File[] filesInThisDirectory = dir.listFiles(new FileFilter() {
+ @Override public boolean accept(File pathname) {
+ return pathname.isFile();
+ }
+ });
+ if (filesInThisDirectory == null) {
+ return;
+ }
+
+ for (File f: filesInThisDirectory) {
+ assertFalse(f.getCanonicalPath(), f.canWrite());
+ }
+ }
+
public void testAllBlockDevicesAreNotReadableWritable() throws Exception {
assertBlockDevicesInDirAndSubDirAreNotWritable(new File("/dev"));
}
@@ -242,8 +283,8 @@
return;
}
- if (!dir.getAbsolutePath().equals(dir.getCanonicalPath())) {
- // don't follow symbolic links.
+ if (isSymbolicLink(dir)) {
+ // don't examine symbolic links.
return;
}
@@ -266,4 +307,8 @@
assertDirectoryAndSubdirectoriesNotWritable(f);
}
}
+
+ private static boolean isSymbolicLink(File f) throws IOException {
+ return !f.getAbsolutePath().equals(f.getCanonicalPath());
+ }
}
diff --git a/tests/tests/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java
index c73d31a..1e12455 100644
--- a/tests/tests/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java
@@ -24,7 +24,6 @@
import android.app.AlertDialog;
import android.content.Context;
import android.test.ActivityInstrumentationTestCase2;
-import android.test.UiThreadTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.Suppress;
import android.view.WindowManager;
@@ -50,47 +49,6 @@
}
/**
- * Verify that adding window of different types in Window Manager requires permissions.
- * <p>Requires Permission:
- * {@link android.Manifest.permission#SYSTEM_ALERT_WINDOW}.
- */
- @UiThreadTest
- @MediumTest
- @Suppress
- @BrokenTest("This test passes, but crashes the UI thread later on. See issues 1909470, 1910487")
- public void testSystemAlertWindow() {
- final int[] types = new int[] {
- WindowManager.LayoutParams.TYPE_PHONE,
- WindowManager.LayoutParams.TYPE_PRIORITY_PHONE,
- WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
- WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
- WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
- };
-
- AlertDialog dialog = (AlertDialog) (mActivity.getDialog());
- // Use normal window type will success
- dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION);
- dialog.show();
-
- // Test special window types which need to be check SYSTEM_ALERT_WINDOW
- // permission.
- for (int i = 0; i < types.length; i++) {
- dialog = (AlertDialog) (mActivity.getDialog());
- dialog.getWindow().setType(types[i]);
- try {
- dialog.show();
- // This throws an exception as expected, but only after already adding
- // a new view to the view hierarchy. This later results in a NullPointerException
- // when the activity gets destroyed. Since that crashes the UI thread and causes
- // test runs to abort, this test is currently excluded.
- fail("Add dialog to Window Manager did not throw BadTokenException as expected");
- } catch (BadTokenException e) {
- // Expected
- }
- }
- }
-
- /**
* Verify that get task requires permissions.
* <p>Requires Permission:
* {@link android.Manifest.permission#GET_TASKS}
diff --git a/tests/tests/telephony/src/android/telephony/cts/PhoneNumberUtilsTest.java b/tests/tests/telephony/src/android/telephony/cts/PhoneNumberUtilsTest.java
index dad3dd6..57e610b 100644
--- a/tests/tests/telephony/src/android/telephony/cts/PhoneNumberUtilsTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/PhoneNumberUtilsTest.java
@@ -159,84 +159,6 @@
}
}
- /**
- * Tests which must be successful both in compareLoosely() and in compareStrictly().
- */
- private void testCompareCommon(final boolean strict) {
- assertTrue(PhoneNumberUtils.compare(null, null, strict));
- assertFalse(PhoneNumberUtils.compare(null, "", strict));
- assertFalse(PhoneNumberUtils.compare("", null, strict));
- assertFalse(PhoneNumberUtils.compare("", "", strict));
-
- assertTrue(PhoneNumberUtils.compare("911", "911", strict));
- assertFalse(PhoneNumberUtils.compare("911", "18005550911", strict));
-
- assertTrue(PhoneNumberUtils.compare("+17005554141", "+17005554141", strict));
- assertTrue(PhoneNumberUtils.compare("+17005554141", "+1 (700).555-4141", strict));
- assertTrue(PhoneNumberUtils.compare("+17005554141", "7005554141", strict));
- assertFalse(PhoneNumberUtils.compare("+1 999 7005554141", "+1 7005554141", strict));
- assertTrue(PhoneNumberUtils.compare("011 1 7005554141", "7005554141", strict));
- assertFalse(PhoneNumberUtils.compare("011 11 7005554141", "+17005554141", strict));
- assertTrue(PhoneNumberUtils.compare("+44 207 792 3490", "0 207 792 3490", strict));
- assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "00 207 792 3490", strict));
-
- // MMI header should be ignored
- assertFalse(PhoneNumberUtils.compare("+17005554141", "**31#17005554141", strict));
- assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "+44 (0) 207 792 3490", strict));
- assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "010 44 207 792 3490", strict));
- assertFalse(PhoneNumberUtils.compare("+44 207 792 3490", "0011 44 207 792 3490", strict));
- assertTrue(PhoneNumberUtils.compare("+444 207 792 3490", "0 207 792 3490", strict));
-
- // make sure SMS short code comparison for numbers less than 7 digits work correctly.
- // For example, "404-04" and "40404" should match because the dialable portion for both
- // numbers are the same.
- assertTrue(PhoneNumberUtils.compare("404-04", "40404", strict));
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "compare",
- args = {String.class, String.class}
- )
- public void testCompareLoosely() {
- testCompareCommon(false);
-
- assertTrue(PhoneNumberUtils.compareLoosely("17005554141", "5554141"));
- assertTrue(PhoneNumberUtils.compareLoosely("+17005554141", "**31#+17005554141"));
- assertFalse(PhoneNumberUtils.compareLoosely("+7(095)9100766", "8(095)9100766"));
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "compare",
- args = {String.class, String.class}
- )
- public void testCompareStrictly() {
- testCompareCommon(true);
-
- // This must be true, since
- // - +7 is russian country calling code
- // - 8 is russian trunk prefix, which should be omitted when being compared to
- // the number with country calling code.
- // - so, this comparation becomes same as comparation between
- // "(095)9100766" v.s."(095)9100766", which is definitely true.
- assertTrue(PhoneNumberUtils.compareStrictly("+7(095)9100766", "8(095)9100766"));
-
- // Test broken caller ID seen on call from Thailand to the US.
- assertTrue(PhoneNumberUtils.compareStrictly("+66811234567", "166811234567"));
-
- // This is not related to Thailand case. NAMP "1" + region code "661".
- assertTrue(PhoneNumberUtils.compareStrictly("16610001234", "6610001234"));
-
- assertFalse(PhoneNumberUtils.compareStrictly("080-1234-5678", "+819012345678"));
- assertTrue(PhoneNumberUtils.compareStrictly("650-000-3456", "16500003456"));
-
- // Phone numbers with Ascii characters, which are common in some countries
- assertFalse(PhoneNumberUtils.compareStrictly("abcd", "bcde"));
- assertTrue(PhoneNumberUtils.compareStrictly("1-800-flowers", "800-flowers"));
- assertFalse(PhoneNumberUtils.compareStrictly("1-800-flowers", "1-800-abcdefg"));
- }
-
@TestTargets({
@TestTargetNew(
level = TestLevel.COMPLETE,
diff --git a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
index f4b74e0..4cfddb1 100644
--- a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
@@ -449,7 +449,7 @@
+ ISO_COUNTRY_CODE_PATTERN,
Pattern.matches(ISO_COUNTRY_CODE_PATTERN, countryCode));
} else {
- assertEquals("", countryCode);
+ // Non-telephony may still have the property defined if it has a SIM.
}
}
@@ -461,7 +461,7 @@
+ ISO_COUNTRY_CODE_PATTERN,
Pattern.matches(ISO_COUNTRY_CODE_PATTERN, countryCode));
} else {
- assertEquals("", countryCode);
+ // Non-telephony may still have the property defined if it has a SIM.
}
}
}
diff --git a/tests/tests/text/src/android/text/cts/LayoutTest.java b/tests/tests/text/src/android/text/cts/LayoutTest.java
index 9d33190..60bac35 100644
--- a/tests/tests/text/src/android/text/cts/LayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/LayoutTest.java
@@ -16,12 +16,12 @@
package android.text.cts;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.ToBeFixed;
+
import android.graphics.Rect;
-import android.graphics.Bitmap.Config;
import android.test.AndroidTestCase;
import android.text.Layout;
import android.text.Spannable;
@@ -30,13 +30,6 @@
import android.text.Layout.Alignment;
import android.text.style.StrikethroughSpan;
-import dalvik.annotation.BrokenTest;
-import dalvik.annotation.TestTargets;
-import dalvik.annotation.TestLevel;
-import dalvik.annotation.TestTargetClass;
-import dalvik.annotation.TestTargetNew;
-import dalvik.annotation.ToBeFixed;
-
@TestTargetClass(Layout.class)
public class LayoutTest extends AndroidTestCase {
private final static int LINE_COUNT = 5;
@@ -84,53 +77,6 @@
@TestTargetNew(
level = TestLevel.COMPLETE,
- method = "draw",
- args = {android.graphics.Canvas.class}
- )
- @ToBeFixed(bug = "1386429", explanation = "can not get the" +
- " package protected class Directions")
- public void testDraw1() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingmult, mSpacingadd);
- layout.draw(new Canvas());
-
- try {
- layout.draw(new Canvas(Bitmap.createBitmap(200, 200, Config.ARGB_4444)));
- fail("should throw NullPointerException here");
- } catch (NullPointerException e) {
- }
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "draw",
- args = {android.graphics.Canvas.class, android.graphics.Path.class,
- android.graphics.Paint.class, int.class}
- )
- @ToBeFixed(bug = "1386429", explanation = "can not get the" +
- " package protected class Directions")
- public void testDraw2() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingmult, mSpacingadd);
- layout.draw(new Canvas(), null, null, 0);
-
- try {
- Bitmap bitmap = Bitmap.createBitmap(200, 200,Config.ARGB_4444);
- layout.draw(new Canvas(bitmap), null, null, 0);
- fail("should throw NullPointerException here");
- } catch (NullPointerException e) {
- }
-
- try {
- Bitmap bitmap = Bitmap.createBitmap(200, 200, null);
- layout.draw(new Canvas(bitmap), new Path(), new Paint(), 2);
- fail("should throw NullPointerException here");
- } catch (NullPointerException e) {
- }
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
method = "getText",
args = {}
)
@@ -282,94 +228,6 @@
@TestTargetNew(
level = TestLevel.COMPLETE,
- method = "getPrimaryHorizontal",
- args = {int.class}
- )
- @ToBeFixed(bug = "1386429", explanation = "can not get the" +
- " package protected class Directions")
- public void testGetPrimaryHorizontal() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingmult, mSpacingadd);
- try {
- layout.getPrimaryHorizontal(0);
- fail("should throw NullPointerException here");
- } catch (NullPointerException e) {
- }
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "getSecondaryHorizontal",
- args = {int.class}
- )
- @ToBeFixed(bug = "1386429", explanation = "can not get the" +
- " package protected class Directions")
- public void testGetSecondaryHorizontal() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingmult, mSpacingadd);
- try {
- layout.getSecondaryHorizontal(0);
- fail("should throw NullPointerException here");
- } catch (NullPointerException e) {
- }
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "getLineLeft",
- args = {int.class}
- )
- public void testGetLineLeft() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingmult, mSpacingadd);
- assertEquals(2.0f, layout.getLineLeft(0));
- assertEquals(4.0f, layout.getLineLeft(1));
- assertEquals(1.0f, layout.getLineLeft(2));
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "getLineRight",
- args = {int.class}
- )
- public void testGetLineRight() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingmult, mSpacingadd);
- assertEquals(9.0f, layout.getLineRight(0));
- assertEquals(7.0f, layout.getLineRight(1));
- assertEquals(10.0f, layout.getLineRight(2));
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "getLineMax",
- args = {int.class}
- )
- @BrokenTest("unsure if asserted widths are correct")
- public void testGetLineMax() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingmult, mSpacingadd);
- assertEquals(6.0f, layout.getLineMax(0));
- assertEquals(3.0f, layout.getLineMax(1));
- assertEquals(9.0f, layout.getLineMax(2));
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "getLineWidth",
- args = {int.class}
- )
- @BrokenTest("unsure if asserted widths are correct")
- public void testGetLineWidth() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingmult, mSpacingadd);
- assertEquals(6.0f, layout.getLineWidth(0));
- assertEquals(3.0f, layout.getLineWidth(1));
- assertEquals(9.0f, layout.getLineWidth(2));
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
method = "getLineForVertical",
args = {int.class}
)
@@ -398,23 +256,6 @@
@TestTargetNew(
level = TestLevel.COMPLETE,
- method = "getOffsetForHorizontal",
- args = {int.class, float.class}
- )
- @ToBeFixed(bug = "1386429", explanation = "can not get the" +
- " package protected class Directions")
- public void testGetOffsetForHorizontal() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingmult, mSpacingadd);
- try {
- layout.getOffsetForHorizontal(0, 0);
- fail("should throw NullPointerException here");
- } catch (NullPointerException e) {
- }
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
method = "getLineEnd",
args = {int.class}
)
@@ -480,84 +321,6 @@
@TestTargetNew(
level = TestLevel.COMPLETE,
- method = "getOffsetToLeftOf",
- args = {int.class}
- )
- @ToBeFixed(bug = "1386429", explanation = "can not get the" +
- " package protected class Directions")
- public void testGetOffsetToLeftOf() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingmult, mSpacingadd);
- try {
- layout.getOffsetToLeftOf(0);
- fail("should throw NullPointerException here");
- } catch (NullPointerException e) {
- }
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "getOffsetToRightOf",
- args = {int.class}
- )
- @ToBeFixed(bug = "1386429", explanation = "can not get the" +
- " package protected class Directions")
- public void testGetOffsetToRightOf() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingmult, mSpacingadd);
- try {
- layout.getOffsetToRightOf(0);
- fail("should throw NullPointerException here");
- } catch (NullPointerException e) {
- }
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "getCursorPath",
- args = {int.class, android.graphics.Path.class, java.lang.CharSequence.class}
- )
- @ToBeFixed(bug = "1386429", explanation = "can not get the" +
- " package protected class Directions")
- public void testGetCursorPath() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingmult, mSpacingadd);
- try {
- layout.getCursorPath(0, new Path(), "test");
- fail("should throw NullPointerException here");
- } catch (NullPointerException e) {
- }
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- method = "getSelectionPath",
- args = {int.class, int.class, android.graphics.Path.class}
- )
- @ToBeFixed(bug = "1386429", explanation = "can not get the" +
- " package protected class Directions")
- public void testGetSelectionPath() {
- Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
- mAlign, mSpacingmult, mSpacingadd);
- Path path = new Path();
-
- layout.getSelectionPath(0, 0, path);
-
- try {
- layout.getSelectionPath(1, 0, path);
- fail("should throw NullPointerException here");
- } catch (NullPointerException e) {
- }
-
- try {
- layout.getSelectionPath(0, 1, path);
- fail("should throw NullPointerException here");
- } catch (NullPointerException e) {
- }
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
method = "getParagraphAlignment",
args = {int.class}
)
diff --git a/tests/tests/text/src/android/text/cts/SelectionTest.java b/tests/tests/text/src/android/text/cts/SelectionTest.java
index 11ba854..4fc3386 100644
--- a/tests/tests/text/src/android/text/cts/SelectionTest.java
+++ b/tests/tests/text/src/android/text/cts/SelectionTest.java
@@ -16,15 +16,16 @@
package android.text.cts;
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.ToBeFixed;
+
import android.test.AndroidTestCase;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.StaticLayout;
import android.text.TextPaint;
-import dalvik.annotation.TestLevel;
-import dalvik.annotation.TestTargetClass;
-import dalvik.annotation.TestTargetNew;
-import dalvik.annotation.ToBeFixed;
@TestTargetClass(Selection.class)
public class SelectionTest extends AndroidTestCase {
@@ -106,12 +107,6 @@
fail("should throw IndexOutOfBoundsException");
} catch (IndexOutOfBoundsException e) {
}
-
- try {
- Selection.setSelection(null, 3, 6);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
}
@TestTargetNew(
@@ -145,12 +140,6 @@
fail("should throw IndexOutOfBoundsException");
} catch (IndexOutOfBoundsException e) {
}
-
- try {
- Selection.setSelection(null, 3);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
}
@TestTargetNew(
@@ -158,7 +147,6 @@
method = "removeSelection",
args = {android.text.Spannable.class}
)
- @ToBeFixed(bug = "1371108",explanation = "throw unexpected NullPointerException")
public void testRemoveSelection() {
CharSequence text = "hello, world";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -176,12 +164,6 @@
Selection.removeSelection(builder);
assertEquals(-1, Selection.getSelectionStart(builder));
assertEquals(-1, Selection.getSelectionEnd(builder));
-
- try {
- Selection.removeSelection(null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
}
@TestTargetNew(
@@ -189,7 +171,6 @@
method = "selectAll",
args = {android.text.Spannable.class}
)
- @ToBeFixed(bug = "1371108",explanation = "throw unexpected NullPointerException")
public void testSelectAll() {
CharSequence text = "hello, world";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -214,12 +195,6 @@
Selection.selectAll(empty);
assertEquals(0, Selection.getSelectionStart(empty));
assertEquals(0, Selection.getSelectionEnd(empty));
-
- try {
- Selection.selectAll(null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
}
@TestTargetNew(
@@ -227,8 +202,6 @@
method = "moveLeft",
args = {android.text.Spannable.class, android.text.Layout.class}
)
- @ToBeFixed(bug = "1417734",explanation = "throw unexpected IndexOutOfBoundsException" +
- "and NullPointerException")
public void testMoveLeft() {
CharSequence text = "hello\nworld";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -236,12 +209,6 @@
assertEquals(-1, Selection.getSelectionStart(builder));
assertEquals(-1, Selection.getSelectionEnd(builder));
- try {
- Selection.moveLeft(builder, layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
-
Selection.setSelection(builder, 6, 8);
assertTrue(Selection.moveLeft(builder, layout));
assertEquals(6, Selection.getSelectionStart(builder));
@@ -260,24 +227,6 @@
assertTrue(Selection.moveLeft(builder, layout));
assertEquals(0, Selection.getSelectionStart(builder));
assertEquals(0, Selection.getSelectionEnd(builder));
-
- try {
- Selection.moveLeft(new SpannableStringBuilder(), layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
-
- try {
- Selection.moveLeft(null, layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
-
- try {
- Selection.moveLeft(builder, null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
}
@TestTargetNew(
@@ -285,8 +234,6 @@
method = "moveRight",
args = {android.text.Spannable.class, android.text.Layout.class}
)
- @ToBeFixed(bug = "1417734",explanation = "throw unexpected IndexOutOfBoundsException" +
- "and NullPointerException")
public void testMoveRight() {
CharSequence text = "hello\nworld";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -294,12 +241,6 @@
assertEquals(-1, Selection.getSelectionStart(builder));
assertEquals(-1, Selection.getSelectionEnd(builder));
- try {
- Selection.moveRight(builder, layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
-
Selection.setSelection(builder,1, 5);
assertTrue(Selection.moveRight(builder, layout));
assertEquals(5, Selection.getSelectionStart(builder));
@@ -322,24 +263,6 @@
assertTrue(Selection.moveRight(builder, layout));
assertEquals(text.length(), Selection.getSelectionStart(builder));
assertEquals(text.length(), Selection.getSelectionEnd(builder));
-
- try {
- Selection.moveRight(null, layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
-
- try {
- Selection.moveRight(builder, null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
- try {
- Selection.moveRight(new SpannableStringBuilder(), layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
}
@TestTargetNew(
@@ -347,7 +270,6 @@
method = "moveUp",
args = {android.text.Spannable.class, android.text.Layout.class}
)
- @ToBeFixed(bug = "1371108",explanation = "throw unexpected NullPointerException")
public void testMoveUp() {
CharSequence text = "Google\nhello,world";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -380,14 +302,6 @@
assertFalse(Selection.moveUp(builder, layout));
assertEquals(5, Selection.getSelectionStart(builder));
assertEquals(5, Selection.getSelectionEnd(builder));
-
- try {
- Selection.moveUp(builder, null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
- Selection.moveUp(null, layout);
}
@TestTargetNew(
@@ -395,8 +309,6 @@
method = "moveDown",
args = {android.text.Spannable.class, android.text.Layout.class}
)
- @ToBeFixed(bug = "1417734",explanation = "throw unexpected IndexOutOfBoundsException" +
- "and NullPointerException")
public void testMoveDown() {
CharSequence text = "hello,world\nGoogle";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -404,12 +316,6 @@
assertEquals(-1, Selection.getSelectionStart(builder));
assertEquals(-1, Selection.getSelectionEnd(builder));
- try {
- Selection.moveDown(builder, layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
-
Selection.setSelection(builder, 1, 3);
assertTrue(Selection.moveDown(builder, layout));
assertEquals(3, Selection.getSelectionStart(builder));
@@ -433,18 +339,6 @@
Selection.moveDown(builder, layout);
assertEquals(18, Selection.getSelectionStart(builder));
assertEquals(18, Selection.getSelectionEnd(builder));
-
- try {
- Selection.moveDown(builder, null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
- try {
- Selection.moveDown(null, layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
}
@TestTargetNew(
@@ -452,8 +346,6 @@
method = "extendSelection",
args = {android.text.Spannable.class, int.class}
)
- @ToBeFixed(bug = "1417734",explanation = "throw unexpected IndexOutOfBoundsException" +
- "and NullPointerException")
public void testExtendSelection() {
CharSequence text = "hello, world";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -505,8 +397,6 @@
method = "extendLeft",
args = {android.text.Spannable.class, android.text.Layout.class}
)
- @ToBeFixed(bug = "1417734",explanation = "throw unexpected IndexOutOfBoundsException" +
- "and NullPointerException")
public void testExtendLeft() {
CharSequence text = "Google\nhello, world";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -514,12 +404,6 @@
assertEquals(-1, Selection.getSelectionStart(builder));
assertEquals(-1, Selection.getSelectionEnd(builder));
- try {
- Selection.extendLeft(builder, layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
-
Selection.setSelection(builder, 7, 8);
assertTrue(Selection.extendLeft(builder, layout));
assertEquals(7, Selection.getSelectionStart(builder));
@@ -537,24 +421,6 @@
assertTrue(Selection.extendLeft(builder, layout));
assertEquals(0, Selection.getSelectionStart(builder));
assertEquals(0, Selection.getSelectionEnd(builder));
-
- try {
- Selection.extendLeft(builder, null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
- try {
- Selection.extendLeft(null, layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
-
- try {
- Selection.extendLeft(new SpannableStringBuilder(), layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
}
@TestTargetNew(
@@ -562,8 +428,6 @@
method = "extendRight",
args = {android.text.Spannable.class, android.text.Layout.class}
)
- @ToBeFixed(bug = "1417734",explanation = "throw unexpected IndexOutOfBoundsException" +
- "and NullPointerException")
public void testExtendRight() {
CharSequence text = "Google\nhello, world";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -571,12 +435,6 @@
assertEquals(-1, Selection.getSelectionStart(builder));
assertEquals(-1, Selection.getSelectionEnd(builder));
- try {
- Selection.extendRight(builder, layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
-
Selection.setSelection(builder, 1, 6);
assertTrue(Selection.extendRight(builder, layout));
assertEquals(1, Selection.getSelectionStart(builder));
@@ -590,24 +448,6 @@
assertTrue(Selection.extendRight(builder, layout));
assertEquals(12, Selection.getSelectionStart(builder));
assertEquals(text.length(), Selection.getSelectionEnd(builder));
-
- try {
- Selection.extendRight(new SpannableStringBuilder(), layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
-
- try {
- Selection.extendRight(builder, null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
- try {
- Selection.extendRight(null, layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
}
@TestTargetNew(
@@ -615,7 +455,6 @@
method = "extendUp",
args = {android.text.Spannable.class, android.text.Layout.class}
)
- @ToBeFixed(bug = "1371108",explanation = "throw unexpected NullPointerException")
public void testExtendUp() {
CharSequence text = "Google\nhello, world";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -640,18 +479,6 @@
assertEquals(8, Selection.getSelectionStart(builder));
assertEquals(0, Selection.getSelectionEnd(builder));
- try {
- Selection.extendUp(builder, null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
- try {
- Selection.extendUp(null, layout);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
builder = new SpannableStringBuilder();
assertTrue(Selection.extendUp(builder, layout));
assertEquals(-1, Selection.getSelectionStart(builder));
@@ -663,8 +490,6 @@
method = "extendDown",
args = {android.text.Spannable.class, android.text.Layout.class}
)
- @ToBeFixed(bug = "1417734",explanation = "throw unexpected IndexOutOfBoundsException" +
- "and NullPointerException")
public void testExtendDown() {
CharSequence text = "Google\nhello, world";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -672,12 +497,6 @@
assertEquals(-1, Selection.getSelectionStart(builder));
assertEquals(-1, Selection.getSelectionEnd(builder));
- try {
- Selection.extendDown(builder, layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
-
Selection.setSelection(builder, 1, 3);
assertTrue(Selection.extendDown(builder, layout));
assertEquals(1, Selection.getSelectionStart(builder));
@@ -690,24 +509,6 @@
assertTrue(Selection.extendDown(builder, layout));
assertEquals(1, Selection.getSelectionStart(builder));
assertEquals(text.length(), Selection.getSelectionEnd(builder));
-
- try {
- Selection.extendDown(new SpannableStringBuilder(), layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
-
- try {
- Selection.extendDown(builder, null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
- try {
- Selection.extendDown(null, layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
}
@TestTargetNew(
@@ -715,7 +516,6 @@
method = "extendToLeftEdge",
args = {android.text.Spannable.class, android.text.Layout.class}
)
- @ToBeFixed(bug = "1371108",explanation = "throw unexpected NullPointerException")
public void testExtendToLeftEdge() {
CharSequence text = "hello\nworld";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -742,18 +542,6 @@
assertEquals(2, Selection.getSelectionStart(builder));
assertEquals(0, Selection.getSelectionEnd(builder));
- try {
- Selection.extendToLeftEdge(new SpannableStringBuilder(), null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
- try {
- Selection.extendToLeftEdge(null, layout);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
builder = new SpannableStringBuilder();
assertEquals(-1, Selection.getSelectionStart(builder));
assertEquals(-1, Selection.getSelectionEnd(builder));
@@ -768,8 +556,6 @@
method = "extendToRightEdge",
args = {android.text.Spannable.class, android.text.Layout.class}
)
- @ToBeFixed(bug = "1417734",explanation = "throw unexpected IndexOutOfBoundsException" +
- "and NullPointerException")
public void testExtendToRightEdge() {
CharSequence text = "hello\nworld";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -794,24 +580,6 @@
assertTrue(Selection.extendToRightEdge(builder, layout));
assertEquals(1, Selection.getSelectionStart(builder));
assertEquals(text.length(), Selection.getSelectionEnd(builder));
-
- try {
- Selection.extendToRightEdge(new SpannableStringBuilder(), layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
-
- try {
- Selection.extendToRightEdge(builder, null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
- try {
- Selection.extendToRightEdge(null, layout);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
}
@TestTargetNew(
@@ -819,7 +587,6 @@
method = "moveToLeftEdge",
args = {android.text.Spannable.class, android.text.Layout.class}
)
- @ToBeFixed(bug = "1371108",explanation = "throw unexpected NullPointerException")
public void testMoveToLeftEdge() {
CharSequence text = "hello\nworld";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -845,18 +612,6 @@
assertEquals(0, Selection.getSelectionStart(builder));
assertEquals(0, Selection.getSelectionEnd(builder));
- try {
- Selection.moveToLeftEdge(builder, null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
- try {
- Selection.moveToLeftEdge(null, layout);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
builder = new SpannableStringBuilder();
assertTrue(Selection.moveToLeftEdge(builder, layout));
assertEquals(0, Selection.getSelectionStart(builder));
@@ -868,8 +623,6 @@
method = "moveToRightEdge",
args = {android.text.Spannable.class, android.text.Layout.class}
)
- @ToBeFixed(bug = "1417734",explanation = "throw unexpected IndexOutOfBoundsException" +
- "and NullPointerException")
public void testMoveToRightEdge() {
CharSequence text = "hello\nworld";
SpannableStringBuilder builder = new SpannableStringBuilder(text);
@@ -894,23 +647,5 @@
assertTrue(Selection.moveToRightEdge(builder, layout));
assertEquals(text.length(), Selection.getSelectionStart(builder));
assertEquals(text.length(), Selection.getSelectionEnd(builder));
-
- try {
- Selection.moveToRightEdge(builder, null);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
- try {
- Selection.moveToRightEdge(null, layout);
- fail("should throw NullPointerException");
- } catch (NullPointerException e) {
- }
-
- try {
- Selection.moveToRightEdge(new SpannableStringBuilder(), layout);
- fail("should throw IndexOutOfBoundsException");
- } catch (IndexOutOfBoundsException e) {
- }
}
}
diff --git a/tests/tests/text/src/android/text/cts/TextUtilsTest.java b/tests/tests/text/src/android/text/cts/TextUtilsTest.java
index 04f9366..b432c1d 100644
--- a/tests/tests/text/src/android/text/cts/TextUtilsTest.java
+++ b/tests/tests/text/src/android/text/cts/TextUtilsTest.java
@@ -139,14 +139,6 @@
} catch (NullPointerException e) {
// issue 1688347, not clear what is supposed to happen if TextPaint is null.
}
-
- try {
- TextUtils.commaEllipsize(text, p, textWidth, "plus 1", null);
- fail("Should throw NullPointerException");
- } catch (NullPointerException e) {
- // issue 1688347, not clear what is supposed to happen
- // if the string for "%d more" in the current locale is null.
- }
}
@TestTargetNew(
@@ -365,11 +357,11 @@
" In other methods, MARQUEE is equivalent to END, except for the first line.")
public void testEllipsize() {
TextPaint p = new TextPaint();
-
+
// turn off kerning. with kerning enabled, different methods of measuring the same text
// produce different results.
p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG);
-
+
CharSequence text = "long string to truncate";
float textWidth = p.measureText(mEllipsis + "uncate");
@@ -391,16 +383,14 @@
TextUtils.ellipsize(text, p, textWidth, TruncateAt.MARQUEE).toString());
textWidth = p.measureText(mEllipsis);
- assertEquals(mEllipsis, TextUtils.ellipsize(text, p, textWidth, TruncateAt.END).toString());
+ assertEquals("", TextUtils.ellipsize(text, p, textWidth, TruncateAt.END).toString());
assertEquals("", TextUtils.ellipsize(text, p, textWidth - 1, TruncateAt.END).toString());
assertEquals("", TextUtils.ellipsize(text, p, -1f, TruncateAt.END).toString());
assertEquals(text,
TextUtils.ellipsize(text, p, Float.MAX_VALUE, TruncateAt.END).toString());
- assertEquals(mEllipsis,
- TextUtils.ellipsize(text, p, textWidth, TruncateAt.START).toString());
- assertEquals(mEllipsis,
- TextUtils.ellipsize(text, p, textWidth, TruncateAt.MIDDLE).toString());
+ assertEquals("", TextUtils.ellipsize(text, p, textWidth, TruncateAt.START).toString());
+ assertEquals("", TextUtils.ellipsize(text, p, textWidth, TruncateAt.MIDDLE).toString());
try {
TextUtils.ellipsize(text, null, textWidth, TruncateAt.MIDDLE);
@@ -436,7 +426,7 @@
// turn off kerning. with kerning enabled, different methods of measuring the same text
// produce different results.
p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG);
-
+
TextUtils.EllipsizeCallback callback = new TextUtils.EllipsizeCallback() {
public void ellipsized(final int start, final int end) {
mStart = start;
@@ -523,14 +513,14 @@
// avail is long enough for ELLIPSIS, and preserveLength is specified.
resetRange();
- assertEquals(getBlankString(true, text.length()),
+ assertEquals(getBlankString(false, text.length()),
TextUtils.ellipsize(text, p, textWidth, TruncateAt.END, true, callback).toString());
assertEquals(0, mStart);
assertEquals(text.length(), mEnd);
// avail is long enough for ELLIPSIS, and preserveLength doesn't be specified.
resetRange();
- assertEquals(mEllipsis,
+ assertEquals("",
TextUtils.ellipsize(text, p, textWidth, TruncateAt.END, false,
callback).toString());
assertEquals(0, mStart);
diff --git a/tests/tests/text/src/android/text/format/cts/DateUtilsTest.java b/tests/tests/text/src/android/text/format/cts/DateUtilsTest.java
index 91bef2d..a5dd335 100644
--- a/tests/tests/text/src/android/text/format/cts/DateUtilsTest.java
+++ b/tests/tests/text/src/android/text/format/cts/DateUtilsTest.java
@@ -198,7 +198,7 @@
})
@SuppressWarnings("deprecation")
public void testFormatMethods() {
- if (!LocaleUtils.isSupportedLocale(Locale.US)) {
+ if (!LocaleUtils.isSupportedLocale(mContext, Locale.US)) {
// Locale is set to US in setUp method.
return;
}
diff --git a/tests/tests/text/src/android/text/method/cts/ArrowKeyMovementMethodTest.java b/tests/tests/text/src/android/text/method/cts/ArrowKeyMovementMethodTest.java
index 1d6c109..a92f323 100644
--- a/tests/tests/text/src/android/text/method/cts/ArrowKeyMovementMethodTest.java
+++ b/tests/tests/text/src/android/text/method/cts/ArrowKeyMovementMethodTest.java
@@ -247,7 +247,8 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
pressBothShiftAlt();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_UP, null));
+ KeyEvent.KEYCODE_DPAD_UP, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_UP)));
// |first line
// second |line
// last line
@@ -256,7 +257,8 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
pressShift();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_UP, null));
+ KeyEvent.KEYCODE_DPAD_UP, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_UP)));
// first lin|e
// second |line
// last line
@@ -267,7 +269,8 @@
pressShift();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_UP, null));
+ KeyEvent.KEYCODE_DPAD_UP, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_UP)));
// |first line
// second |line
// last line
@@ -276,7 +279,8 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
pressAlt();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_UP, null));
+ KeyEvent.KEYCODE_DPAD_UP, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_UP)));
// |first line
// second line
// last line
@@ -285,14 +289,16 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
MetaKeyKeyListener.resetMetaState(mEditable);
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_UP, null));
+ KeyEvent.KEYCODE_DPAD_UP, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_UP)));
// first lin|e
// second line
// last line
assertSelection(correspondingIn1stLine);
assertFalse(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_UP, null));
+ KeyEvent.KEYCODE_DPAD_UP, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_UP)));
// first lin|e
// second line
// last line
@@ -316,7 +322,8 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
pressBothShiftAlt();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_DOWN, null));
+ KeyEvent.KEYCODE_DPAD_DOWN, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_DOWN)));
// first line
// second |line
// last line|
@@ -325,7 +332,8 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
pressShift();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_DOWN, null));
+ KeyEvent.KEYCODE_DPAD_DOWN, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_DOWN)));
// first line
// second |line
// last lin|e
@@ -336,7 +344,8 @@
pressShift();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_DOWN, null));
+ KeyEvent.KEYCODE_DPAD_DOWN, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_DOWN)));
// first line
// second |line
// last line|
@@ -345,7 +354,8 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
pressAlt();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_DOWN, null));
+ KeyEvent.KEYCODE_DPAD_DOWN, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_DOWN)));
// first line
// second line
// last line|
@@ -354,14 +364,16 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
MetaKeyKeyListener.resetMetaState(mEditable);
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_DOWN, null));
+ KeyEvent.KEYCODE_DPAD_DOWN, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_DOWN)));
// first line
// second line
// last lin|e
assertSelection(correspondingIn3rdLine);
assertFalse(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_DOWN, null));
+ KeyEvent.KEYCODE_DPAD_DOWN, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_DOWN)));
// first line
// second line
// last lin|e
@@ -385,7 +397,8 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
pressBothShiftAlt();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_LEFT, null));
+ KeyEvent.KEYCODE_DPAD_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT)));
// first line
// |second |line
// last line
@@ -393,7 +406,8 @@
pressBothShiftAlt();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_LEFT, null));
+ KeyEvent.KEYCODE_DPAD_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT)));
// first line
// |second |line
// last line
@@ -402,7 +416,8 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
pressShift();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_LEFT, null));
+ KeyEvent.KEYCODE_DPAD_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT)));
// first line
// second| |line
// last line
@@ -410,7 +425,8 @@
pressShift();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_LEFT, null));
+ KeyEvent.KEYCODE_DPAD_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT)));
// first line
// secon|d |line
// last line
@@ -419,7 +435,8 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
pressAlt();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_LEFT, null));
+ KeyEvent.KEYCODE_DPAD_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT)));
// first line
// |second line
// last line
@@ -427,7 +444,8 @@
pressAlt();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_LEFT, null));
+ KeyEvent.KEYCODE_DPAD_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT)));
// first line
// |second line
// last line
@@ -436,7 +454,8 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
MetaKeyKeyListener.resetMetaState(mEditable);
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_LEFT, null));
+ KeyEvent.KEYCODE_DPAD_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT)));
// first line
// second| line
// last line
@@ -447,7 +466,8 @@
// |second line
// last line
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_LEFT, null));
+ KeyEvent.KEYCODE_DPAD_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT)));
// first line|
// second line
// last line
@@ -471,7 +491,8 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
pressBothShiftAlt();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_RIGHT, null));
+ KeyEvent.KEYCODE_DPAD_RIGHT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_RIGHT)));
// first line
// second |line|
// last line
@@ -479,7 +500,8 @@
pressBothShiftAlt();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_RIGHT, null));
+ KeyEvent.KEYCODE_DPAD_RIGHT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_RIGHT)));
// first line
// second |line|
// last line
@@ -488,7 +510,8 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
pressShift();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_RIGHT, null));
+ KeyEvent.KEYCODE_DPAD_RIGHT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_RIGHT)));
// first line
// second |l|ine
// last line
@@ -496,7 +519,8 @@
pressShift();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_RIGHT, null));
+ KeyEvent.KEYCODE_DPAD_RIGHT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_RIGHT)));
// first line
// second |li|ne
// last line
@@ -505,7 +529,8 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
pressAlt();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_RIGHT, null));
+ KeyEvent.KEYCODE_DPAD_RIGHT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_RIGHT)));
// first line
// second line|
// last line
@@ -513,7 +538,8 @@
pressAlt();
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_RIGHT, null));
+ KeyEvent.KEYCODE_DPAD_RIGHT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_RIGHT)));
// first line
// second line|
// last line
@@ -522,7 +548,8 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
MetaKeyKeyListener.resetMetaState(mEditable);
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_RIGHT, null));
+ KeyEvent.KEYCODE_DPAD_RIGHT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_RIGHT)));
// first line
// second l|ine
// last line
@@ -533,7 +560,8 @@
// second line|
// last line
assertTrue(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_RIGHT, null));
+ KeyEvent.KEYCODE_DPAD_RIGHT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_RIGHT)));
// first line
// second line
// |last line
@@ -624,43 +652,15 @@
Selection.setSelection(mEditable, SPACE_IN_2ND_LINE);
assertFalse(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_DPAD_CENTER, null));
+ KeyEvent.KEYCODE_DPAD_CENTER, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_CENTER)));
assertFalse(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_0, null));
+ KeyEvent.KEYCODE_0, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0)));
assertFalse(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_E, null));
+ KeyEvent.KEYCODE_E, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_E)));
assertFalse(mArrowKeyMovementMethod.onKeyDown(mTextView, mEditable,
- KeyEvent.KEYCODE_UNKNOWN, null));
- }
-
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- notes = "Test {@link ArrowKeyMovementMethod#onKeyDown(TextView, Spannable, int, "
- + "KeyEvent)}. Test the method with null parameters.",
- method = "onKeyDown",
- args = {TextView.class, Spannable.class, int.class, KeyEvent.class}
- )
- @ToBeFixed(bug = "1695243", explanation = "Android API javadocs are incomplete. @throws clause "
- + "should be added into javadoc of ArrowKeyMovementMethod#onKeyDown(TextView, "
- + "Spannable, int, KeyEvent)} when the params view or buffer is null")
- public void testOnKeyDownWithNullParameters() {
- initTextViewWithNullLayout();
- mEditable = (Editable) mTextView.getText();
- try {
- mArrowKeyMovementMethod.onKeyDown(null, mEditable, KeyEvent.KEYCODE_DPAD_RIGHT,
- new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT));
- fail("The method did not throw NullPointerException when param textView is null.");
- } catch (NullPointerException e) {
- // expected
- }
-
- try {
- mArrowKeyMovementMethod.onKeyDown(mTextView, null, KeyEvent.KEYCODE_DPAD_RIGHT,
- new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT));
- fail("The method did not throw NullPointerException when param spannable is null.");
- } catch (NullPointerException e) {
- // expected
- }
+ KeyEvent.KEYCODE_UNKNOWN, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_UNKNOWN)));
}
@TestTargetNew(
diff --git a/tests/tests/text/src/android/text/method/cts/LinkMovementMethodTest.java b/tests/tests/text/src/android/text/method/cts/LinkMovementMethodTest.java
index 57c6d33..cbfc327 100644
--- a/tests/tests/text/src/android/text/method/cts/LinkMovementMethodTest.java
+++ b/tests/tests/text/src/android/text/method/cts/LinkMovementMethodTest.java
@@ -129,7 +129,7 @@
Selection.setSelection(spannable, 0, spannable.length());
assertSelection(spannable, 0, spannable.length());
- assertEquals(2, spannable.getSpans(0, spannable.length(), Object.class).length);
+ assertTrue("Expected at least 2 spans", 2 <= spannable.getSpans(0, spannable.length(), Object.class).length);
method.onTakeFocus(null, spannable, View.FOCUS_UP);
assertSelection(spannable, -1);
assertEquals(1, spannable.getSpans(0, spannable.length(), Object.class).length);
@@ -141,7 +141,7 @@
// focus forwards
Selection.setSelection(spannable, 0, spannable.length());
assertSelection(spannable, 0, spannable.length());
- assertEquals(3, spannable.getSpans(0, spannable.length(), Object.class).length);
+ assertTrue("Expected at least 3 spans", 3 <= spannable.getSpans(0, spannable.length(), Object.class).length);
method.onTakeFocus(null, spannable, View.FOCUS_RIGHT);
assertSelection(spannable, -1);
assertEquals(0, spannable.getSpans(0, spannable.length(), Object.class).length);
@@ -151,7 +151,7 @@
// param direction is unknown(0)
Selection.setSelection(spannable, 0, spannable.length());
assertSelection(spannable, 0, spannable.length());
- assertEquals(3, spannable.getSpans(0, spannable.length(), Object.class).length);
+ assertTrue("Expected at least 3 spans", 3 <= spannable.getSpans(0, spannable.length(), Object.class).length);
method.onTakeFocus(null, spannable, 0);
assertSelection(spannable, -1);
assertEquals(0, spannable.getSpans(0, spannable.length(), Object.class).length);
@@ -576,7 +576,7 @@
Selection.setSelection(spannable, 0, spannable.length());
assertSelection(spannable, 0, spannable.length());
- assertEquals(3, spannable.getSpans(0, spannable.length(), Object.class).length);
+ assertTrue("Expected at least 3 spans", 3 <= spannable.getSpans(0, spannable.length(), Object.class).length);
method.initialize(null, spannable);
assertSelection(spannable, -1);
assertEquals(0, spannable.getSpans(0, spannable.length(), Object.class).length);
diff --git a/tests/tests/text/src/android/text/method/cts/ScrollingMovementMethodTest.java b/tests/tests/text/src/android/text/method/cts/ScrollingMovementMethodTest.java
index 9968bda..cdb1409 100644
--- a/tests/tests/text/src/android/text/method/cts/ScrollingMovementMethodTest.java
+++ b/tests/tests/text/src/android/text/method/cts/ScrollingMovementMethodTest.java
@@ -486,8 +486,9 @@
int previousScrollX = mTextView.getScrollX();
runActionOnUiThread(new Runnable() {
public void run() {
- method.onKeyDown(mTextView, null, KeyEvent.KEYCODE_DPAD_RIGHT,
- new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT));
+ method.onKeyDown(mTextView, (Spannable) mTextView.getText(),
+ KeyEvent.KEYCODE_DPAD_RIGHT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_RIGHT));
}
});
assertTrue(mTextView.getScrollX() > previousScrollX);
@@ -495,8 +496,9 @@
previousScrollX = mTextView.getScrollX();
runActionOnUiThread(new Runnable() {
public void run() {
- method.onKeyDown(mTextView, null, KeyEvent.KEYCODE_DPAD_LEFT,
- new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT));
+ method.onKeyDown(mTextView, (Spannable) mTextView.getText(),
+ KeyEvent.KEYCODE_DPAD_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT));
}
});
assertTrue(mTextView.getScrollX() < previousScrollX);
@@ -505,7 +507,8 @@
assertVisibleLineInTextView(0);
runActionOnUiThread(new Runnable() {
public void run() {
- assertFalse(method.onKeyDown(mTextView, mSpannable, 0, null));
+ assertFalse(method.onKeyDown(mTextView, mSpannable, 0,
+ new KeyEvent(KeyEvent.ACTION_DOWN, 0)));
}
});
assertEquals(previousScrollX, mTextView.getScrollX());
diff --git a/tests/tests/text/src/android/text/util/cts/LinkifyTest.java b/tests/tests/text/src/android/text/util/cts/LinkifyTest.java
index 0733b9f..2c45c77 100644
--- a/tests/tests/text/src/android/text/util/cts/LinkifyTest.java
+++ b/tests/tests/text/src/android/text/util/cts/LinkifyTest.java
@@ -16,18 +16,15 @@
package android.text.util.cts;
-import dalvik.annotation.BrokenTest;
import dalvik.annotation.TestLevel;
import dalvik.annotation.TestTargetClass;
import dalvik.annotation.TestTargetNew;
-import dalvik.annotation.ToBeFixed;
import android.test.AndroidTestCase;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.URLSpan;
import android.text.util.Linkify;
-//import android.text.util.Regex;
import android.text.util.Linkify.MatchFilter;
import android.text.util.Linkify.TransformFilter;
import android.widget.TextView;
@@ -90,7 +87,6 @@
method = "addLinks",
args = {android.text.Spannable.class, int.class}
)
- @ToBeFixed(bug = "1417734", explanation = "NullPointerException issue")
public void testAddLinks1() {
SpannableString spannable = new SpannableString("name@gmail.com, "
+ "123456789, tel:(0812)1234567 "
@@ -129,7 +125,6 @@
method = "addLinks",
args = {android.widget.TextView.class, int.class}
)
- @ToBeFixed(bug = "1417734", explanation = "NullPointerException issue")
public void testAddLinks2() {
String text = "www.google.com, name@gmail.com";
TextView tv = new TextView(mContext);
@@ -164,7 +159,6 @@
args = {android.widget.TextView.class, java.util.regex.Pattern.class,
java.lang.String.class}
)
- @ToBeFixed(bug = "1417734", explanation = "NullPointerException issue")
public void testAddLinks3() {
String text = "Alan, Charlie";
TextView tv = new TextView(mContext);
@@ -216,8 +210,6 @@
java.lang.String.class, android.text.util.Linkify.MatchFilter.class,
android.text.util.Linkify.TransformFilter.class}
)
- @ToBeFixed(bug = "1417734", explanation = "NullPointerException issue")
- @BrokenTest("Filter and pattern need to be fixed")
public void testAddLinks4() {
TextView tv = new TextView(mContext);
@@ -226,8 +218,9 @@
Linkify.addLinks(tv, LINKIFY_TEST_PATTERN, "Test:",
mMatchFilterStartWithDot, mTransformFilterUpperChar);
URLSpan[] spans = ((Spannable) tv.getText()).getSpans(0, text.length(), URLSpan.class);
- assertEquals(1, spans.length);
+ assertEquals(2, spans.length);
assertEquals("test:ilterpperase.pattern", spans[0].getURL());
+ assertEquals("test:12", spans[1].getURL());
try {
Linkify.addLinks((TextView) null, LINKIFY_TEST_PATTERN, "Test:",
@@ -249,21 +242,24 @@
Linkify.addLinks(tv, LINKIFY_TEST_PATTERN, null,
mMatchFilterStartWithDot, mTransformFilterUpperChar);
spans = ((Spannable) tv.getText()).getSpans(0, text.length(), URLSpan.class);
- assertEquals(1, spans.length);
+ assertEquals(2, spans.length);
assertEquals("ilterpperase.pattern", spans[0].getURL());
+ assertEquals("12", spans[1].getURL());
tv.setText(text);
Linkify.addLinks(tv, LINKIFY_TEST_PATTERN, "Test:", null, mTransformFilterUpperChar);
spans = ((Spannable) tv.getText()).getSpans(0, text.length(), URLSpan.class);
- assertEquals(2, spans.length);
+ assertEquals(3, spans.length);
assertEquals("test:ilterpperase.pattern", spans[0].getURL());
- assertEquals("test:345.pattern", spans[1].getURL());
+ assertEquals("test:12", spans[1].getURL());
+ assertEquals("test:345.pattern", spans[2].getURL());
tv.setText(text);
Linkify.addLinks(tv, LINKIFY_TEST_PATTERN, "Test:", mMatchFilterStartWithDot, null);
spans = ((Spannable) tv.getText()).getSpans(0, text.length(), URLSpan.class);
- assertEquals(1, spans.length);
+ assertEquals(2, spans.length);
assertEquals("test:FilterUpperCase.pattern", spans[0].getURL());
+ assertEquals("test:12", spans[1].getURL());
}
@TestTargetNew(
@@ -272,7 +268,6 @@
method = "addLinks",
args = {android.text.Spannable.class, java.util.regex.Pattern.class, java.lang.String.class}
)
- @ToBeFixed(bug = "1417734", explanation = "NullPointerException issue")
public void testAddLinks5() {
String text = "google.pattern, test:AZ0101.pattern";
@@ -312,8 +307,6 @@
android.text.util.Linkify.MatchFilter.class,
android.text.util.Linkify.TransformFilter.class}
)
- @ToBeFixed(bug = "1417734", explanation = "NullPointerException issue")
- @BrokenTest("Filter and pattern need to be fixed")
public void testAddLinks6() {
String text = "FilterUpperCase.pattern, 12.345.pattern";
@@ -321,8 +314,9 @@
Linkify.addLinks(spannable, LINKIFY_TEST_PATTERN, "Test:",
mMatchFilterStartWithDot, mTransformFilterUpperChar);
URLSpan[] spans = (spannable.getSpans(0, spannable.length(), URLSpan.class));
- assertEquals(1, spans.length);
+ assertEquals(2, spans.length);
assertEquals("test:ilterpperase.pattern", spans[0].getURL());
+ assertEquals("test:12", spans[1].getURL());
try {
Linkify.addLinks((Spannable)null, LINKIFY_TEST_PATTERN, "Test:",
@@ -344,20 +338,23 @@
Linkify.addLinks(spannable, LINKIFY_TEST_PATTERN, null, mMatchFilterStartWithDot,
mTransformFilterUpperChar);
spans = (spannable.getSpans(0, spannable.length(), URLSpan.class));
- assertEquals(1, spans.length);
+ assertEquals(2, spans.length);
assertEquals("ilterpperase.pattern", spans[0].getURL());
+ assertEquals("12", spans[1].getURL());
spannable = new SpannableString(text);
Linkify.addLinks(spannable, LINKIFY_TEST_PATTERN, "Test:", null, mTransformFilterUpperChar);
spans = (spannable.getSpans(0, spannable.length(), URLSpan.class));
- assertEquals(2, spans.length);
+ assertEquals(3, spans.length);
assertEquals("test:ilterpperase.pattern", spans[0].getURL());
- assertEquals("test:345.pattern", spans[1].getURL());
+ assertEquals("test:12", spans[1].getURL());
+ assertEquals("test:345.pattern", spans[2].getURL());
spannable = new SpannableString(text);
Linkify.addLinks(spannable, LINKIFY_TEST_PATTERN, "Test:", mMatchFilterStartWithDot, null);
spans = (spannable.getSpans(0, spannable.length(), URLSpan.class));
- assertEquals(1, spans.length);
+ assertEquals(2, spans.length);
assertEquals("test:FilterUpperCase.pattern", spans[0].getURL());
+ assertEquals("test:12", spans[1].getURL());
}
}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
index cf20217..ff10ca5 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
@@ -115,42 +115,6 @@
@TestTargets({
@TestTargetNew(
level = TestLevel.COMPLETE,
- method = "onReceivedIcon",
- args = {WebView.class, Bitmap.class}
- )
- })
- public void testOnReceivedIcon() throws Throwable {
- final MockWebChromeClient webChromeClient = new MockWebChromeClient();
- mWebView.setWebChromeClient(webChromeClient);
-
- runTestOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- // getInstance must run on the UI thread
- WebIconDatabase mIconDb = WebIconDatabase.getInstance();
- String dbPath = getActivity().getFilesDir().toString() + "/icons";
- mIconDb.open(dbPath);
- mIconDb.removeAllIcons();
- }
- });
-
- assertFalse(webChromeClient.hadOnReceivedIcon());
-
- String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
- mWebView.loadUrl(url);
-
- new DelayedCheck(TEST_TIMEOUT) {
- @Override
- protected boolean check() {
- return webChromeClient.hadOnReceivedIcon();
- }
- }.run();
- }
-
- @TestTargets({
- @TestTargetNew(
- level = TestLevel.COMPLETE,
method = "onCreateWindow",
args = {WebView.class, boolean.class, boolean.class, Message.class}
),
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java b/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
index 4f73cdc..016d566 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
@@ -27,6 +27,7 @@
import android.test.ActivityInstrumentationTestCase2;
import android.view.animation.cts.DelayedCheck;
import android.webkit.WebBackForwardList;
+import android.webkit.WebChromeClient;
import android.webkit.WebHistoryItem;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@@ -75,6 +76,7 @@
})
public void testWebHistoryItem() {
final WebView view = getActivity().getWebView();
+ view.setWebChromeClient(new WebChromeClient());
WebBackForwardList list = view.copyBackForwardList();
assertEquals(0, list.getSize());
@@ -111,6 +113,7 @@
@BrokenTest(value = "Bug 2121787: Test times out on the host side. Not 100% reproducible.")
public void testRedirect() throws InterruptedException {
final WebView view = getActivity().getWebView();
+ view.setWebChromeClient(new WebChromeClient());
// set the web view client so that redirects are loaded in the WebView itself
view.setWebViewClient(new WebViewClient());
WebBackForwardList list = view.copyBackForwardList();
@@ -137,15 +140,6 @@
// assertEquals(redirect, item.getOriginalUrl());
}
- @TestTargetNew(
- level = TestLevel.NOT_FEASIBLE,
- notes = "clone() is protected and WebHistoryItem cannot be subclassed",
- method = "clone",
- args = {}
- )
- public void testClone() {
- }
-
private void assertLoadUrlSuccessfully(final WebView view, String url) {
view.loadUrl(url);
// wait for the page load to complete
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
index e290972..9f4f8d0 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
@@ -90,8 +90,8 @@
public void testUserAgentString_default() {
final String actualUserAgentString = mSettings.getUserAgentString();
Log.i(LOG_TAG, String.format("Checking user agent string %s", actualUserAgentString));
- final String patternString = "Mozilla/5\\.0 \\(Linux; U; Android (.+); (\\w+)-(\\w+);" +
- "(.+)\\s?Build/(.+)\\) AppleWebKit/(\\d+)\\.(\\d+) \\(KHTML, like Gecko\\) Version/4\\.0" +
+ final String patternString = "Mozilla/5\\.0 \\(Linux; U; Android (.+); (\\w+)-(\\w+);\\s?" +
+ "(.*)\\sBuild/(.+)\\) AppleWebKit/(\\d+)\\.(\\d+) \\(KHTML, like Gecko\\) Version/4\\.0" +
"( Mobile)? Safari/(\\d+)\\.(\\d+)";
Log.i(LOG_TAG, String.format("Trying to match pattern %s", patternString));
final Pattern userAgentExpr = Pattern.compile(patternString);
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
index 49d68c6..5344339 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
@@ -193,7 +193,7 @@
mWebServer = new CtsTestServer(getActivity());
assertFalse(webViewClient.hasOnReceivedHttpAuthRequestCalled());
- String url = mWebServer.getAuthAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
+ String url = mWebServer.getAuthAssetUrl(TestHtmlConstants.EMBEDDED_IMG_URL);
assertLoadUrlSuccessfully(mWebView, url);
assertTrue(webViewClient.hasOnReceivedHttpAuthRequestCalled());
}
diff --git a/tests/tests/widget/src/android/widget/cts/GridViewTest.java b/tests/tests/widget/src/android/widget/cts/GridViewTest.java
index eb9782c..802aa96 100644
--- a/tests/tests/widget/src/android/widget/cts/GridViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/GridViewTest.java
@@ -677,50 +677,6 @@
assertEquals(child0.getLeft(), child1.getLeft());
}
- @TestTargets({
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- notes = "Test {@link GridView#computeVerticalScrollExtent()}",
- method = "computeVerticalScrollExtent",
- args = {}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- notes = "Test {@link GridView#computeVerticalScrollExtent()}",
- method = "computeVerticalScrollOffset",
- args = {}
- ),
- @TestTargetNew(
- level = TestLevel.COMPLETE,
- notes = "Test {@link GridView#computeVerticalScrollExtent()}",
- method = "computeVerticalScrollRange",
- args = {}
- )
- })
- public void testScroll() throws Throwable {
- final MockGridView mockGridView= new MockGridView(mActivity);
- final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- // this test case can not be ran in UI thread.
- runTestOnUiThread(new Runnable() {
- public void run() {
- mActivity.getWindow().setContentView(mockGridView, params);
- mockGridView.setAdapter(new ImageAdapter(mActivity));
- }
- });
- mInstrumentation.waitForIdleSync();
- TouchUtils.scrollToTop(this, mActivity, mockGridView);
-
- int oldRange = mockGridView.computeVerticalScrollRange();
- int oldExtent = mockGridView.computeVerticalScrollExtent();
- int oldOffset = mockGridView.computeVerticalScrollOffset();
-
- TouchUtils.scrollToBottom(this, mActivity, mockGridView);
- assertEquals(oldRange, mockGridView.computeVerticalScrollRange());
- assertEquals(oldExtent, mockGridView.computeVerticalScrollExtent());
- assertTrue(oldOffset < mockGridView.computeVerticalScrollOffset());
- }
-
private static class MockGridView extends GridView {
private boolean mCalledOnMeasure = false;
private boolean mCalledOnFocusChanged = false;
diff --git a/tools/dx-tests/Android.mk b/tools/dx-tests/Android.mk
index 9dee47d..9bdef58 100644
--- a/tools/dx-tests/Android.mk
+++ b/tools/dx-tests/Android.mk
@@ -38,7 +38,7 @@
@echo "Copy: $(PRIVATE_MODULE) ($@)"
$(copy-file-to-new-target)
$(hide) chmod 755 $@
- @$(PRIVATE_CURRENT_MODULE_SCRIPT) "$(PRIVATE_BASE)" "$(HOST_JAVAC)" "$(PRIVATE_INTERMEDIATES)" "$(HOST_OUT_JAVA_LIBRARIES)/dx.jar:$(HOST_OUT_JAVA_LIBRARIES)/cfassembler.jar"
+ @$(PRIVATE_CURRENT_MODULE_SCRIPT) "$(PRIVATE_BASE)" "$(HOST_JAVAC)" "$(PRIVATE_INTERMEDIATES)" "$(HOST_OUT_JAVA_LIBRARIES)/dx.jar:$(HOST_OUT_JAVA_LIBRARIES)/cfassembler.jar" "$(HOST_OUT)"
# cfassembler host module
#============================================================
diff --git a/tools/dx-tests/etc/compileall b/tools/dx-tests/etc/compileall
index 42b198e..cb95b4e 100755
--- a/tools/dx-tests/etc/compileall
+++ b/tools/dx-tests/etc/compileall
@@ -54,6 +54,7 @@
javac=$2
tmpdir=$3 # ANDROID_BUILD_TOP/$3
dxjarpath=$4
+outdir=$5
project_src=$project_home/src
project_lib=$project_home/lib
project_data=$project_home/data
@@ -103,7 +104,7 @@
javac -d $javac_out -classpath $project_lib/junit.jar:$javac_out -sourcepath $mainfilesdir \@$mainfileslist
# now copy relevant data from intermediates dir to its final destination
-fdest=$ANDROID_BUILD_TOP/out/target/common/cts/dxconverter
+fdest=$outdir/cts/dxconverter
mkdir -p $fdest/data
acp -r $javac_out $fdest/
acp $mainfilesdir/data/scriptdata $fdest/data/scriptdata
diff --git a/tools/dx-tests/etc/starttests b/tools/dx-tests/etc/starttests
index e43a00a..afa55a8 100755
--- a/tools/dx-tests/etc/starttests
+++ b/tools/dx-tests/etc/starttests
@@ -74,7 +74,7 @@
debug_opts="-Xcheck:jni"
exe=$base/system/bin/dalvikvm
bpath=$framework/core.jar
-BASEDIR=$ANDROID_BUILD_TOP/out/target/common/cts/dxconverter
+BASEDIR=$progdir/../cts/dxconverter
echo "--------------------------------------------------"
echo "DX Converter Test Suite"
diff --git a/tools/host/src/com/android/cts/Version.java b/tools/host/src/com/android/cts/Version.java
index fead909..0426560 100644
--- a/tools/host/src/com/android/cts/Version.java
+++ b/tools/host/src/com/android/cts/Version.java
@@ -18,12 +18,12 @@
public class Version {
// The CTS version string
- private static final String version = "2.2_r1";
-
+ private static final String version = "2.3_r1";
+
private Version() {
// no instances allowed
}
-
+
public static String asString() {
return version;
}
diff --git a/tools/tradefed-host/etc/Android.mk b/tools/tradefed-host/etc/Android.mk
new file mode 100644
index 0000000..f3bee3f
--- /dev/null
+++ b/tools/tradefed-host/etc/Android.mk
@@ -0,0 +1,22 @@
+# Copyright (C) 2011 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PREBUILT_EXECUTABLES := cts-tradefed
+include $(BUILD_HOST_PREBUILT)
+
diff --git a/tools/tradefed-host/etc/cts-tradefed b/tools/tradefed-host/etc/cts-tradefed
new file mode 100755
index 0000000..82eebe7
--- /dev/null
+++ b/tools/tradefed-host/etc/cts-tradefed
@@ -0,0 +1,76 @@
+#!/bin/bash
+
+# Copyright (C) 2011 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.
+
+# launcher script for cts-tradefed harness
+# can be used from an Android build environment, or a standalone cts zip
+
+checkFile() {
+ if [ ! -f "$1" ]; then
+ echo "Unable to locate $1"
+ exit
+ fi;
+}
+
+checkPath() {
+ if ! type -P $1 &> /dev/null; then
+ echo "Unable to find $1 in path."
+ exit
+ fi;
+}
+
+checkPath adb
+checkPath java
+
+# check java version
+JAVA_VERSION=$(java -version 2>&1 | head -n 1 | grep '[ "]1\.6[\. "$$]')
+if [ "${JAVA_VERSION}" == "" ]; then
+ echo "Wrong java version. 1.6 is required."
+ exit
+fi
+
+# check if in Android build env
+if [ ! -z ${ANDROID_BUILD_TOP} ]; then
+ HOST=`uname`
+ if [ "$HOST" == "Linux" ]; then
+ OS="linux-x86"
+ elif [ "$HOST" == "Darwin" ]; then
+ OS="darwin-x86"
+ else
+ echo "Unrecognized OS"
+ exit
+ fi;
+ CTS_ROOT=${ANDROID_BUILD_TOP}/out/host/${OS}/cts/android-cts
+ if [ ! -d ${CTS_ROOT} ]; then
+ echo "Could not find $CTS_ROOT in Android build environment. Try 'make cts'"
+ exit
+ fi;
+fi;
+
+if [ -z ${CTS_ROOT} ]; then
+ # assume we're in an extracted cts install
+ CTS_ROOT="$(dirname $0)/.."
+fi;
+
+JAR_DIR=${CTS_ROOT}/tools
+JARS="ddmlib-prebuilt.jar tradefed-prebuilt.jar hosttestlib.jar cts-tradefed.jar"
+
+for JAR in $JARS; do
+ checkFile ${JAR_DIR}/${JAR}
+ JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}
+done
+
+java -cp ${JAR_PATH} com.android.tradefed.command.Command "$@" --cts-install-path ${CTS_ROOT} cts
+
diff --git a/tools/utils/Android.mk b/tools/utils/Android.mk
index 5a4597f..0782116 100644
--- a/tools/utils/Android.mk
+++ b/tools/utils/Android.mk
@@ -24,4 +24,6 @@
LOCAL_CLASSPATH := $(HOST_JDK_TOOLS_JAR) $(LOCAL_PATH)/lib/junit.jar
+LOCAL_STATIC_JAVA_LIBRARIES := vogarexpectlib
+
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/utils/CollectAllTests.java b/tools/utils/CollectAllTests.java
index 7628ba2..cb109e8 100644
--- a/tools/utils/CollectAllTests.java
+++ b/tools/utils/CollectAllTests.java
@@ -18,11 +18,13 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
+import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -43,6 +45,10 @@
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
+import vogar.ExpectationStore;
+import vogar.Expectation;
+import vogar.ModeId;
+
public class CollectAllTests extends DescriptionGenerator {
static final String ATTRIBUTE_RUNNER = "runner";
@@ -97,10 +103,12 @@
private static String MANIFESTFILE = "";
private static String TESTSUITECLASS = "";
private static String ANDROID_MAKE_FILE = "";
+ private static String EXPECTATION_DIR = null;
private static Test TESTSUITE;
static XMLGenerator xmlGenerator;
+ private static ExpectationStore vogarExpectationStore;
public static void main(String[] args) {
if (args.length > 2) {
@@ -108,11 +116,14 @@
MANIFESTFILE = args [1];
TESTSUITECLASS = args[2];
if (args.length > 3) {
- ANDROID_MAKE_FILE = args[3];
+ EXPECTATION_DIR = args[3];
+ }
+ if (args.length > 4) {
+ ANDROID_MAKE_FILE = args[4];
}
} else {
System.out.println("usage: \n" +
- "\t... CollectAllTests <output-file> <manifest-file> <testsuite-class-name> <makefile-file>");
+ "\t... CollectAllTests <output-file> <manifest-file> <testsuite-class-name> <makefile-file> <expectation-dir>");
System.exit(1);
}
@@ -183,6 +194,14 @@
System.exit(1);
}
+ try {
+ vogarExpectationStore = provideExpectationStore(EXPECTATION_DIR);
+ } catch (IOException e) {
+ System.err.println("Can't initialize vogar expectation store");
+ e.printStackTrace(System.err);
+ System.exit(1);
+ }
+
testCases = new LinkedHashMap<String, TestClass>();
CollectAllTests cat = new CollectAllTests();
cat.compose();
@@ -302,6 +321,20 @@
return getAnnotation(testClass, testName, SUPPRESSED_TEST) != null;
}
+ private boolean hasSideEffects(final Class<? extends TestCase> testClass,
+ final String testName) {
+ return getAnnotation(testClass, testName, SIDE_EFFECT) != null;
+ }
+
+ private boolean isVogarKnownFailure(final Class<? extends TestCase> testClass,
+ final String testName) {
+ if (vogarExpectationStore == null) {
+ return false;
+ }
+ String fullTestName = String.format("%s#%s", testClass.getName(), testName);
+ return vogarExpectationStore.get(fullTestName) != Expectation.SUCCESS;
+ }
+
private String getAnnotation(final Class<? extends TestCase> testClass,
final String testName, final String annotationName) {
try {
@@ -349,6 +382,12 @@
} else if (isSuppressed(test.getClass(), testName)) {
System.out.println("ignoring suppressed test: " + test);
return;
+ } else if (hasSideEffects(test.getClass(), testName)) {
+ System.out.println("ignoring test with side effects: " + test);
+ return;
+ } else if (isVogarKnownFailure(test.getClass(), testName)) {
+ System.out.println("ignoring vogar known failure: " + test);
+ return;
}
if (!testName.startsWith("test")) {
@@ -377,4 +416,26 @@
failed.add(test.getClass().getName());
}
}
+
+ public static ExpectationStore provideExpectationStore(String dir) throws IOException {
+ if (dir == null) {
+ return null;
+ }
+ ExpectationStore result = ExpectationStore.parse(getExpectationFiles(dir), ModeId.DEVICE);
+ return result;
+ }
+
+ private static Set<File> getExpectationFiles(String dir) {
+ Set<File> expectSet = new HashSet<File>();
+ File[] files = new File(dir).listFiles(new FilenameFilter() {
+ // ignore obviously temporary files
+ public boolean accept(File dir, String name) {
+ return !name.endsWith("~") && !name.startsWith(".");
+ }
+ });
+ if (files != null) {
+ expectSet.addAll(Arrays.asList(files));
+ }
+ return expectSet;
+ }
}
diff --git a/tools/utils/DescriptionGenerator.java b/tools/utils/DescriptionGenerator.java
index 99be6dc..2d58543 100644
--- a/tools/utils/DescriptionGenerator.java
+++ b/tools/utils/DescriptionGenerator.java
@@ -65,6 +65,7 @@
static final String HOST_CONTROLLER = "dalvik.annotation.HostController";
static final String KNOWN_FAILURE = "dalvik.annotation.KnownFailure";
static final String BROKEN_TEST = "dalvik.annotation.BrokenTest";
+ static final String SIDE_EFFECT = "dalvik.annotation.SideEffect";
static final String SUPPRESSED_TEST = "android.test.suitebuilder.annotation.Suppress";
static final String JUNIT_TEST_CASE_CLASS_NAME = "junit.framework.testcase";
diff --git a/tools/utils/startcts b/tools/utils/startcts
index c648125..b0cb9ef 100755
--- a/tools/utils/startcts
+++ b/tools/utils/startcts
@@ -14,12 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-if [ -z "${SDK_ROOT}" ]; then
-# CONFIGURATION
-# Set this variable to the root of your Android SDK installation.
-SDK_ROOT=NOT_CONFIGURED
-fi;
-
if [ -z "${CTS_ROOT}" ]; then
# CONFIGURATION
# Set this variable to the root of unzipped CTS directory
@@ -46,26 +40,35 @@
fi;
}
+checkPath() {
+ if ! type -P $1 &> /dev/null; then
+ echo "Unable to find $1 in path."
+ exit
+ fi;
+}
+
checkDir ${CTS_ROOT} "Error: Cannot locate CTS in \"${CTS_DIR}\". Please check your configuration in $0"
-checkDir ${SDK_ROOT} "Error: Cannot locate SDK installation in \"${SDK_ROOT}\". Please check your configuration in $0"
DDM_LIB=${CTS_ROOT}/tools/ddmlib-prebuilt.jar
CTS_LIB=${CTS_ROOT}/tools/cts.jar
JUNIT_LIB=${CTS_ROOT}/tools/junit.jar
HOSTTEST_LIB=${CTS_ROOT}/tools/hosttestlib.jar
CTS_TEST_ANNOTATIONS_HOST_LIB=${CTS_ROOT}/tools/CtsTestAnnotationsHostLib.jar
-ADB_PATH=${SDK_ROOT}/tools
-ADB_EXE=${ADB_PATH}/adb
checkFile ${DDM_LIB}
checkFile ${CTS_LIB}
checkFile ${JUNIT_LIB}
checkFile ${HOSTTEST_LIB}
-checkFile ${ADB_EXE}
JARS=${CTS_LIB}:${DDM_LIB}:${JUNIT_LIB}:${HOSTTEST_LIB}:${CTS_TEST_ANNOTATIONS_HOST_LIB}
-PATH=${ADB_PATH}:${PATH}
+# Add SDK_ROOT to the PATH for backwards compatibility with prior startcts
+# commands that required SDK_ROOT to find adb.
+if [ -n "${SDK_ROOT}" ]; then
+ PATH=${SDK_ROOT}/platform-tools:${SDK_ROOT}/tools:${PATH}
+fi
+
+checkPath adb
# options for the JVM
JAVA_OPTS="-Xmx512M"