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"