Merge "Skip Bluetooth tests if the device doesn't support Bluetooth." into gingerbread
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 582b70e..2dabc00 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -22,13 +22,15 @@
       
     <uses-sdk android:minSdkVersion="5"></uses-sdk>
 
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     
     <!-- Needed by the Audio Quality Verifier to store the sound samples that will be mailed. -->
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
-    <application android:label="@string/app_name" android:icon="@drawable/icon">
+    <application android:label="@string/app_name" android:icon="@drawable/icon" android:debuggable="true">
 
         <activity android:name=".CtsVerifierActivity" android:label="@string/app_name">
             <intent-filter>
@@ -41,6 +43,27 @@
         
         <provider android:name=".TestResultsProvider" 
                 android:authorities="com.android.cts.verifier.testresultsprovider" />
+                
+        <activity android:name=".bluetooth.BluetoothTestActivity"
+                android:label="@string/bluetooth_test"
+                android:configChanges="keyboardHidden|orientation">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_networking" />
+        </activity>
+        
+        <activity android:name=".bluetooth.BluetoothToggleActivity"
+                android:label="@string/bt_toggle_bluetooth"
+                android:configChanges="keyboardHidden|orientation" />
+
+        <activity android:name=".bluetooth.DevicePickerActivity"
+                android:label="@string/bt_device_picker"
+                android:configChanges="keyboardHidden|orientation" />
+
+        <activity android:name=".bluetooth.MessageTestActivity"
+                android:configChanges="keyboardHidden|orientation" />
 
         <activity android:name=".suid.SuidFilesActivity" 
                 android:label="@string/suid_files"
diff --git a/apps/CtsVerifier/arduino-helper/Makefile b/apps/CtsVerifier/arduino-helper/Makefile
deleted file mode 100644
index 4159ee6..0000000
--- a/apps/CtsVerifier/arduino-helper/Makefile
+++ /dev/null
@@ -1,274 +0,0 @@
-# Copyright 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
-
-# NOTE: this Makefile is derived from an example included in Arduino 0017.
-# That Makefile bore no license, but the rest of the Arduino is generally LGPL
-# v2 with upgrade option. Since it was unclear what the license for the
-# Makefile itself was intended to be, this version uses ASL2.0, as LGPL is
-# an unusual license choice for a Makefile. ASL2.0 is compatible with LGPL v3
-# so there should be no conflict (due to the upgrade option.)
-
-# Theory of Operation
-# 'Arduino' refers to a hardware platform, and a software IDE. The IDE is
-# pretty simple, and boils down to a crude text editor and some build
-# plumbing. An Arduino 'sketch' is the core of a C (or C++) program; the build
-# plumbing wraps a bunch of boilerplate (like #includes and entry point stubs)
-# around the user's core code, and handles running the gcc cross-compiler and
-# linking in the appropriate goodies to target AVR. It also handles
-# programming the Atmel part on the board, via USB.
-# 
-# The function of this Makefile is simply to replicate all the boilerplate
-# management. An Arduino 'sketch' is stored as a .pde file, but is essentially
-# C code; so this Makefile cats together a full C/C++ file, compiles the
-# standard Arduino libraries, and links them all together, using the AVR
-# cross-compiler toolchain for gcc. (Ubuntu provides a pre-built avr-gcc
-# package.)
-
-# Path settings
-TARGET = $(notdir $(CURDIR))
-ARDUINO_PATH=/usr/share/arduino
-ARDUINO_VERSION=18
-ARDUINO_CORES_PATH = $(ARDUINO_PATH)/hardware/arduino/cores/arduino
-AVR_TOOLS_PATH = /usr/bin
-AVRDUDE_PATH = /usr/bin
-
-# Programming port information
-PORT = /dev/ttyUSB0
-UPLOAD_RATE = 57600
-
-# Target information (settings below work for Duemilanove)
-AVRDUDE_PROGRAMMER = stk500v1
-MCU = atmega328p
-F_CPU = 16000000
-OUT_DIR = out
-
-# C-language libraries to compile and link with all programs
-ARDUINO_C_MODULES_SRC =  \
-$(ARDUINO_CORES_PATH)/wiring_pulse.c \
-$(ARDUINO_CORES_PATH)/wiring_analog.c \
-$(ARDUINO_CORES_PATH)/pins_arduino.c \
-$(ARDUINO_CORES_PATH)/wiring.c \
-$(ARDUINO_CORES_PATH)/wiring_digital.c \
-$(ARDUINO_CORES_PATH)/WInterrupts.c \
-$(ARDUINO_CORES_PATH)/wiring_shift.c
-ARDUINO_C_MODULES_OBJ = \
-$(OUT_DIR)/wiring_pulse.o \
-$(OUT_DIR)/wiring_analog.o \
-$(OUT_DIR)/pins_arduino.o \
-$(OUT_DIR)/wiring.o \
-$(OUT_DIR)/wiring_digital.o \
-$(OUT_DIR)/WInterrupts.o \
-$(OUT_DIR)/wiring_shift.o
-
-# C++-language libraries to compile and link with all programs
-ARDUINO_CPP_MODULES_SRC = \
-$(ARDUINO_CORES_PATH)/Tone.cpp \
-$(ARDUINO_CORES_PATH)/main.cpp \
-$(ARDUINO_CORES_PATH)/WMath.cpp \
-$(ARDUINO_CORES_PATH)/Print.cpp \
-$(ARDUINO_CORES_PATH)/HardwareSerial.cpp
-ARDUINO_CPP_MODULES_OBJ = \
-$(OUT_DIR)/Tone.o \
-$(OUT_DIR)/main.o \
-$(OUT_DIR)/WMath.o \
-$(OUT_DIR)/Print.o \
-$(OUT_DIR)/HardwareSerial.o
-
-TARGET_CPP_SRC = $(OUT_DIR)/$(TARGET).cpp
-FORMAT = ihex
-
-# Name of this Makefile (used for "make depend").
-MAKEFILE = Makefile
-
-# Debugging format.
-# Native formats for AVR-GCC's -g are stabs [default], or dwarf-2.
-# AVR (extended) COFF requires stabs, plus an avr-objcopy run.
-DEBUG =
-OPT = s
-
-# Place -D or -U options here
-CDEFS = -DF_CPU=$(F_CPU)L -DARDUINO=$(ARDUINO_VERSION)
-CXXDEFS = -DF_CPU=$(F_CPU)L -DARDUINO=$(ARDUINO_VERSION)
-
-# Place -I options here
-CINCS = -I$(ARDUINO_CORES_PATH)
-CXXINCS = -I$(ARDUINO_CORES_PATH)
-
-# toolchain flags
-CDEBUG = -g$(DEBUG)
-CWARN = -w      # suppress all warnings
-CTUNING = -ffunction-sections -fdata-sections
-CXXTUNING = -fno-exceptions -ffunction-sections -fdata-sections
-CFLAGS = $(CDEBUG) -O$(OPT) $(CWARN) $(CTUNING) $(CDEFS) $(CINCS) $(CSTANDARD)
-CXXFLAGS = $(CDEBUG) -O$(OPT) $(CWARN) $(CXXTUNING) $(CDEFS) $(CINCS)
-#ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs
-LDFLAGS = -O$(OPT) -lm -Wl,--gc-sections
-
-# Programming support using avrdude. Settings and variables.
-AVRDUDE_WRITE_FLASH = -U flash:w:$(OUT_DIR)/$(TARGET).hex
-#AVRDUDE_FLAGS = -V -F -C $(ARDUINO_PATH)/hardware/tools/avrdude.conf \
-
-AVRDUDE_FLAGS = -V -F -p $(MCU) -P $(PORT) -c $(AVRDUDE_PROGRAMMER) -b $(UPLOAD_RATE)
-
-# AVR cross-compiler toolchain binaries
-CC = $(AVR_TOOLS_PATH)/avr-gcc
-CXX = $(AVR_TOOLS_PATH)/avr-g++
-LD = $(AVR_TOOLS_PATH)/avr-gcc
-OBJCOPY = $(AVR_TOOLS_PATH)/avr-objcopy
-OBJDUMP = $(AVR_TOOLS_PATH)/avr-objdump
-AR  = $(AVR_TOOLS_PATH)/avr-ar
-SIZE = $(AVR_TOOLS_PATH)/avr-size
-NM = $(AVR_TOOLS_PATH)/avr-nm
-AVRDUDE = $(AVRDUDE_PATH)/avrdude
- 
-# General programs
-REMOVE = rm -f
-RMDIR = rmdir
-MV = mv -f
-
-# Combine all necessary flags and optional flags.
-# Add target processor to flags.
-ALL_CFLAGS = $(CFLAGS) -mmcu=$(MCU)
-ALL_CXXFLAGS = $(CXXFLAGS) -mmcu=$(MCU)
-ALL_ASFLAGS = -x assembler-with-cpp $(ASFLAGS) -mmcu=$(MCU)
-ALL_LDFLAGS = $(LDFLAGS) -mmcu=$(MCU)
-
-# Default target.
-all: $(OUT_DIR)_files build sizeafter
-build: elf hex
-
-# Concatenates the 'sketch' .pde file with the usual Arduino boilerplate
-$(OUT_DIR)/$(TARGET).cpp: $(TARGET).pde
-	@echo "Generate $(TARGET).cpp from $(TARGET).pde"
-	@test -d $(OUT_DIR) || mkdir $(OUT_DIR)
-	@echo '#include "WProgram.h"' > $(OUT_DIR)/$(TARGET).cpp
-	@echo 'void setup();' >> $(OUT_DIR)/$(TARGET).cpp
-	@echo 'void loop();' >> $(OUT_DIR)/$(TARGET).cpp
-	@cat $(TARGET).pde >> $(OUT_DIR)/$(TARGET).cpp
-
-elf: $(OUT_DIR)/$(TARGET).elf
-hex: $(OUT_DIR)/$(TARGET).hex
-eep: $(OUT_DIR)/$(TARGET).eep
-lss: $(OUT_DIR)/$(TARGET).lss
-sym: $(OUT_DIR)/$(TARGET).sym
-
-# Program the device.  
-upload: $(OUT_DIR)/$(TARGET).hex
-	# pulsedtr strobes the DTR line to tell arduino to enter pgming mode
-	python ./pulsedtr.py $(PORT)
-	$(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH)
-
-
-# Display size of file.
-HEXSIZE = $(SIZE) --target=$(FORMAT) $(OUT_DIR)/$(TARGET).hex
-ELFSIZE = $(SIZE)  $(OUT_DIR)/$(TARGET).elf
-sizebefore:
-	@if [ -f $(OUT_DIR)/$(TARGET).elf ]; then echo $(MSG_SIZE_BEFORE); $(HEXSIZE); fi
-
-sizeafter:
-	@if [ -f $(OUT_DIR)/$(TARGET).elf ]; then echo $(MSG_SIZE_AFTER); $(HEXSIZE); fi
-
-
-# Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB.
-COFFCONVERT=$(OBJCOPY) --debugging \
---change-section-address .data-0x800000 \
---change-section-address .bss-0x800000 \
---change-section-address .noinit-0x800000 \
---change-section-address .eeprom-0x810000
-
-
-coff: $(OUT_DIR)/$(TARGET).elf
-	$(COFFCONVERT) -O coff-avr $(OUT_DIR)/$(TARGET).elf $(TARGET).cof
-
-
-extcoff: $(TARGET).elf
-	$(COFFCONVERT) -O coff-ext-avr $(OUT_DIR)/$(TARGET).elf $(TARGET).cof
-
-
-.SUFFIXES: .elf .hex .eep .lss .sym
-
-.elf.hex:
-	$(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@
-
-.elf.eep:
-	$(OBJCOPY) -O $(FORMAT) -j .eeprom --set-section-flags=.eeprom="alloc,load" \
-	--no-change-warnings \
-	--change-section-lma .eeprom=0 $< $@
-
-# Create extended listing file from ELF output file.
-.elf.lss:
-	$(OBJDUMP) -h -S $< > $@
-
-# Create a symbol table from ELF output file.
-.elf.sym:
-	$(NM) -n $< > $@
-
-	# Link: create ELF output file from library.
-$(OUT_DIR)/$(TARGET).elf: $(OUT_DIR)/$(TARGET).o $(OUT_DIR)/core.a
-	$(LD) $(ALL_LDFLAGS) -o $@ $(OUT_DIR)/$(TARGET).o $(OUT_DIR)/core.a
-
-$(OUT_DIR)/core.a: $(ARDUINO_C_MODULES_OBJ) $(ARDUINO_CPP_MODULES_OBJ)
-	@for i in $(ARDUINO_C_MODULES_OBJ) $(ARDUINO_CPP_MODULES_OBJ); do echo $(AR) rcs $(OUT_DIR)/core.a $$i; $(AR) rcs $(OUT_DIR)/core.a $$i; done
-
-$(ARDUINO_C_MODULES_OBJ):
-	$(CC) -c $(ALL_CFLAGS) $(ARDUINO_CORES_PATH)/$(patsubst %.o,%.c,$(patsubst $(OUT_DIR)/%,%,$@)) -o $@
-
-$(ARDUINO_CPP_MODULES_OBJ):
-	$(CXX) -c $(ALL_CXXFLAGS) $(ARDUINO_CORES_PATH)/$(patsubst %.o,%.cpp,$(patsubst $(OUT_DIR)/%,%,$@)) -o $@
-
-# Compile: create object files from C++ source files.
-.cpp.o:
-	$(CXX) -c $(ALL_CXXFLAGS) $< -o $@
-
-# Compile: create object files from C source files.
-.c.o:
-	$(CC) -c $(ALL_CFLAGS) $< -o $@
-
-
-# Compile: create assembler files from C source files.
-.c.s:
-	$(CC) -S $(ALL_CFLAGS) $< -o $@
-
-
-# Assemble: create object files from assembler source files.
-.S.o:
-	$(CC) -c $(ALL_ASFLAGS) $< -o $@
-
-
-# Automatic dependencies
-%.d: %.c
-	$(CC) -M $(ALL_CFLAGS) $< | sed "s;$(notdir $*).o:;$*.o $*.d:;" > $@
-
-%.d: %.cpp
-	$(CXX) -M $(ALL_CXXFLAGS) $< | sed "s;$(notdir $*).o:;$*.o $*.d:;" > $@
-
-
-# Target: clean project.
-clean:
-	$(REMOVE) $(OUT_DIR)/$(TARGET).hex $(OUT_DIR)/$(TARGET).eep \
-        $(OUT_DIR)/$(TARGET).cof $(OUT_DIR)/$(TARGET).elf \
-        $(OUT_DIR)/$(TARGET).map $(OUT_DIR)/$(TARGET).sym \
-        $(OUT_DIR)/$(TARGET).lss $(OUT_DIR)/core.a \
-	$(ARDUINO_C_MODULES_SRC:.c=.s) $(ARDUINO_C_MODULES_SRC:.c=.d) \
-        $(ARDUINO_CPP_MODULES_SRC:.cpp=.s) $(ARDUINO_CPP_MODULES_SRC:.cpp=.d) \
-        $(ARDUINO_C_MODULES_OBJ) $(ARDUINO_CPP_MODULES_OBJ) \
-        $(OUT_DIR)/$(TARGET).cpp $(OUT_DIR)/$(TARGET).o
-distclean: clean
-	$(RMDIR) $(OUT_DIR)
-
-.PHONY: all build elf hex eep lss sym program coff extcoff clean $(OUT_DIR)_files sizebefore sizeafter
-
-#include $(ARDUINO_C_MODULES_SRC:.c=.d)
-#include $(ARDUINO_CPP_MODULES_SRC:.cpp=.d) 
-#include $(TARGET_CPP_SRC:.cpp=.d) 
diff --git a/apps/CtsVerifier/arduino-helper/README b/apps/CtsVerifier/arduino-helper/README
deleted file mode 100644
index 8ad1037..0000000
--- a/apps/CtsVerifier/arduino-helper/README
+++ /dev/null
@@ -1,53 +0,0 @@
-The code in this directory is intended to be run on an Arduino, which is a
-simple, open-source microcontroller platform:
-    http://arduino.org/
-
-Essentially, an Arduino is a board for an ATMega microcontroller, and a GUI
-IDE project intended for semi-technical developers. The Arduino software
-essentially boils down to an IDE which is a text editor & build automater for a
-C/C++ library that hides most of the low-level details of configuring a
-program to run on the microcontroller. It uses the AVR toolchain and
-programmer to work with the device from a host PC. This project includes a
-Makefile which replicates work the normally performed by the IDE; that is, it
-builds and links the relevant Arduino boilerplate code that sets up the
-microcontroller, and then links together a final .hex file and prepares it for
-upload to the device.
-
-A BlueSMiRF Bluetooth SPP-to-TTL (or similar) board is also required; this
-device is used to validate Bluetooth API support via the CTS Verifier app.
-    http://www.sparkfun.com/commerce/product_info.php?products_id=582
-
-This code running on a simple, inexpensive, known-good microcontroller
-platform makes it much easier to validate the Bluetooth API for an Android
-candidate device than the "Bluetooth Chat" approach currently required by the
-CDD.
-
-A compatible device can be substituted for some or all of the Arduino
-configuration; however it's worth noting that for approximately US$110, a
-complete test rig can be completed and used with any number of devices under
-test. Organizations are, of course, also free to manufacture their own
-Arduino-like devices.
-
-This code (and especially the Makefile) requires version 0018 of the Arduino
-toolchain. Because this software uses a non-Android-standard build, the
-Android source tree includes in the prebuilt/ directory a .hex file compiled
-from this source suitable for directly flashing to an Arduino
-Duemilanove-compatible device.
-
-The file pulsedtr.py is a Python script using the Serial I/O API that strobes
-the DTR line on the serial port. This is a signal to the Arduino to enter
-programming mode; this script is thus used in flashing the Arduino board (via
-a direct USB connection) with the prebuilt .hex file. If you are building
-from source, 'make upload' will handle this for you.
-
-To build the .hex file on Ubuntu Lucid:
-$ sudo add-apt-repository ppa:arduino-ubuntu-team
-$ sudo apt-get update; sudo apt-get install arduino
-$ make
-$ make upload
-
-Alternatively, the file 'cts-verifier.pde' can be loaded into the Arduino IDE
-as a sketch using the normal GUI workflow.
-
-This Makefile should hypothetically work on a Mac with the appropriate Arduino
-toolchain installed, but it has not been tested. Windows is not supported.
diff --git a/apps/CtsVerifier/arduino-helper/arduino-helper.pde b/apps/CtsVerifier/arduino-helper/arduino-helper.pde
deleted file mode 100644
index 7d56096..0000000
--- a/apps/CtsVerifier/arduino-helper/arduino-helper.pde
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * Copyright 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
- */
-
-/*
- * Define the basic structure for messages from the host.
- * Messages are MESSAGE_SIZE bytes, with a 2-byte opcode, a 2-byte
- * unique ID defined by the sender, and the remainder payload.
- * The remaining 2 bytes must be 0xFEEDFACE. This is used by
- * the message handler as a tail sentinel to resync with the
- * sender in case data is lost and the fixed-byte messages
- * get out of sync.
- */
-#define MESSAGE_SIZE 128 // serial buffer size on TTL is 128 bytes
-#define MESSAGE_DELIMITER 0xBEEF
-#define MESSAGE_ESCAPE 0x2a
-struct message {
-  uint16_t opcode;
-  uint16_t id;
-  uint8_t data[MESSAGE_SIZE - 6];
-  uint16_t tail;
-};
-struct message CURRENT_MESSAGE;
-
-#define OPCODE_RESET 0x00
-struct reset {
-  uint16_t opcode;
-  uint16_t id;
-  uint8_t unused[MESSAGE_SIZE - 4];
-};
-
-#define OPCODE_INIT_TIME (OPCODE_RESET + 1)
-struct init_time {
-  uint16_t opcode;
-  uint16_t id;
-  uint32_t cur_raw_time;
-  uint8_t unused[MESSAGE_SIZE - 6];
-};
-
-#define OPCODE_CURRENT_TIME (OPCODE_RESET + 2)
-struct current_time { // we never actually use this, but here for consistency
-  uint16_t opcode;
-  uint16_t id;
-  uint8_t unused[MESSAGE_SIZE - 4];
-};
-
-#define OPCODE_SETMODE_PONG (OPCODE_RESET + 3)
-struct setmode_pong {
-  uint16_t opcode;
-  uint16_t id;
-  uint16_t playfield_width;
-  uint16_t playfield_height;
-  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;
-  uint8_t unused[MESSAGE_SIZE - 4];
-};
-
-struct wall_time_struct {
-  uint32_t raw;
-  uint8_t initialized;
-  uint8_t hours;
-  uint8_t minutes;
-  uint8_t seconds;
-};
-struct wall_time_struct WALL_TIME;
-
-/*
- * Main ball-playing state record.
- */
-struct pong_state_struct {
-  uint16_t playfield_width;
-  uint16_t playfield_height;
-  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;
-
-
-/* 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);
-    Serial.print(":");
-    if (WALL_TIME.minutes < 10)
-      Serial.print("0");
-    Serial.print(WALL_TIME.minutes, DEC);
-    Serial.print(":");
-    if (WALL_TIME.seconds < 10)
-      Serial.print("0");
-    Serial.println(WALL_TIME.seconds, DEC);
-  } else {
-    Serial.println("current_time=00:00:00");
-  }
-  Serial.print("\r\n");
-}
-
-
-/*
- * Opcode processor/handler.
- */
-void handle_current_message() {
-  static uint16_t last_id = 999999;
-  static struct setmode_pong* setmode_pong_msg;
-
-  if (CURRENT_MESSAGE.id == last_id) {
-    return;
-  }
-  last_id = CURRENT_MESSAGE.id;
-
-  switch (CURRENT_MESSAGE.opcode) {
-
-    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.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);
-      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: // 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;
-      }
-
-      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);
-      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;
-  }
-}
-
-
-/*
- * Pumps the message processor. That is, this function is intended to be
- * called once per loop to read all pending Serial/TTL data, and decode a
- * message from the peer if one is complete. In cases where data is corrupted
- * (such as by dropping bytes), this function also attempts to re-sync with
- * the host by discarding messages until it finds a MESSAGE_DELIMITER, after
- * which is resyncs its buffer on the first subsequent byte.
- *
- * This functional also handles two low-level 'system' messages: a reset
- * instruction which invokes reset(), and an init_time instruction which
- * provides the soft clock with the current time so that it can start keeping
- * time.
- */
-void pump_message_processor() {
-  static uint8_t cur_byte;
-  static uint16_t* cur_word;
-  static int8_t delimiter_index;
-  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;
-    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
-        // buffer for the delimiter, discard previous message, and shift
-        // partial next message to front of buffer. This loses a message but
-        // gets us back in sync
-        delimiter_index = -2;
-        for (int i = MESSAGE_SIZE - 2; i >= 0; --i) {
-          if (*((uint16_t*)(MESSAGE_BUFFER.buffer + i)) == MESSAGE_DELIMITER) {
-            if (((i - 1) >= 0) && (MESSAGE_BUFFER.buffer[i - 1] != MESSAGE_ESCAPE)) {
-              delimiter_index = i;
-              break;
-            }
-          }
-        }
-        MESSAGE_BUFFER.count = 0;
-        if (delimiter_index >= 0) {
-          for (int i = delimiter_index + 2; i < MESSAGE_SIZE; ++i, ++(MESSAGE_BUFFER.count)) {
-            MESSAGE_BUFFER.buffer[MESSAGE_BUFFER.count] = MESSAGE_BUFFER.buffer[i];
-          }
-        }
-        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));
-      }
-    }
-  }
-}
-
-
-/*
- * Pumps the system wall clock. This checks the device's monotonic clock to
- * determine elapsed time since last invocation, and updates wall clock time
- * by dead reckoning. Since the device has no battery backup, a power-off will
- * lose the current time, so timekeeping cannot begin until an INIT_TIME
- * message is received. (The pump_message_processor() function handles that.)
- *
- * Once timekeeping is underway, current time is exposed to user/app code via
- * the WALL_TIME object, which has 24-hour HH/MM/SS fields.
- */
-void pump_clock() {
-  static uint32_t prev_millis = 0;
-  static uint32_t tmp_prev_millis = 0;
-  uint32_t tmp = 0;
-
-  if (millis() / 1000 != tmp_prev_millis) {
-    tmp_prev_millis = millis() / 1000;
-  }
-
-  if (WALL_TIME.initialized) {
-    tmp = millis() / 1000;
-    if (tmp != prev_millis) {
-      prev_millis = tmp;
-      WALL_TIME.raw++;
-    }
-    WALL_TIME.seconds = WALL_TIME.raw % 60;
-    WALL_TIME.minutes = (WALL_TIME.raw / 60) % 60;
-    WALL_TIME.hours = (WALL_TIME.raw / (60 * 60)) % 24;
-  }
-}
-
-
-/*
- * Standard Arduino setup hook.
- */
-void setup() {
-  Serial.begin(115200);
-}
-
-
-/*
- * Standard Arduino loop-pump hook.
- */
-void loop() {
-  pump_clock();
-  pump_message_processor();
-  handle_current_message();
-}
diff --git a/apps/CtsVerifier/arduino-helper/pulsedtr.py b/apps/CtsVerifier/arduino-helper/pulsedtr.py
deleted file mode 100644
index 887700f..0000000
--- a/apps/CtsVerifier/arduino-helper/pulsedtr.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 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
-
-import serial
-import time
-import sys
-
-if len(sys.argv) != 2:
-    print "Please specify a port, e.g. %s /dev/ttyUSB0" % sys.argv[0]
-    sys.exit(-1)
-
-ser = serial.Serial(sys.argv[1])
-ser.setDTR(1)
-time.sleep(0.25)
-ser.setDTR(0)
-ser.close()
diff --git a/apps/CtsVerifier/res/layout/bt_device_name.xml b/apps/CtsVerifier/res/layout/bt_device_name.xml
new file mode 100644
index 0000000..c37eece
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/bt_device_name.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:padding="5dp"
+        />
diff --git a/apps/CtsVerifier/res/layout/bt_device_picker.xml b/apps/CtsVerifier/res/layout/bt_device_picker.xml
new file mode 100644
index 0000000..ecca0e5
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/bt_device_picker.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        >
+
+    <TextView android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/bt_paired_devices"
+            style="?android:attr/listSeparatorTextViewStyle"
+            />
+    <FrameLayout android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            >
+        <ListView android:id="@+id/bt_paired_devices"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                />
+        <TextView android:id="@+id/bt_empty_paired_devices"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="center"
+                android:text="@string/bt_no_devices"
+                android:visibility="gone"
+                />
+    </FrameLayout>
+
+    <TextView android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/bt_new_devices"
+            style="?android:attr/listSeparatorTextViewStyle"
+            />
+    <FrameLayout android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            >
+        <ListView android:id="@+id/bt_new_devices"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                />
+        <TextView android:id="@+id/bt_empty_new_devices"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/bt_no_devices"
+                android:visibility="gone"
+                />
+    </FrameLayout>
+            
+    <Button android:id="@+id/bt_scan_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:drawableTop="@android:drawable/ic_menu_search"
+            android:text="@string/bt_scan"            
+            />
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/bt_main.xml b/apps/CtsVerifier/res/layout/bt_main.xml
new file mode 100644
index 0000000..cb65412
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/bt_main.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+         android:orientation="vertical"
+         android:layout_width="match_parent"
+         android:layout_height="match_parent"
+         >
+
+    <ListView android:id="@id/android:list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            />
+
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/bt_message_row.xml b/apps/CtsVerifier/res/layout/bt_message_row.xml
new file mode 100644
index 0000000..c37eece
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/bt_message_row.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:padding="5dp"
+        />
diff --git a/apps/CtsVerifier/res/layout/bt_messages.xml b/apps/CtsVerifier/res/layout/bt_messages.xml
new file mode 100644
index 0000000..cb46811
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/bt_messages.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<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:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/bt_sent_messages"
+            style="?android:attr/listSeparatorTextViewStyle"
+            />
+    <FrameLayout android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            >
+        <ListView android:id="@+id/bt_sent_messages"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                />
+        <TextView android:id="@+id/bt_empty_sent_messages"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="center"
+                android:visibility="gone"
+                />
+    </FrameLayout>
+
+    <TextView android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/bt_received_messages"
+            style="?android:attr/listSeparatorTextViewStyle"
+            />
+    <FrameLayout android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            >
+        <ListView android:id="@+id/bt_received_messages"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                />
+        <TextView android:id="@+id/bt_empty_received_messages"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="center"
+                android:visibility="gone"
+                />
+    </FrameLayout>
+
+    <Button android:id="@+id/bt_make_discoverable_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:drawableTop="@android:drawable/ic_menu_mylocation"
+            android:text="@string/bt_make_discoverable"
+            />
+            
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/bt_toggle.xml b/apps/CtsVerifier/res/layout/bt_toggle.xml
new file mode 100644
index 0000000..3a07514
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/bt_toggle.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:padding="10dip"
+        >
+    <TextView android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:gravity="center"
+            android:text="@string/bt_toggle_instructions"
+            android:textSize="24dip"
+            />
+
+    <ToggleButton android:id="@+id/bt_toggle_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            android:textOn="@string/bt_disable_bluetooth"
+            android:textOff="@string/bt_enable_bluetooth"
+            />
+            
+    <include android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            layout="@layout/pass_fail_buttons" 
+            />
+
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 987bef7..dc6cf20 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -32,6 +32,7 @@
     <!-- Strings for TestListActivity -->
     <string name="test_list_title">Manual Test List</string>
     <string name="test_category_audio">Audio</string>
+    <string name="test_category_networking">Networking</string>
     <string name="test_category_sensors">Sensors</string>
     <string name="test_category_security">Security</string>
     <string name="test_category_features">Features</string>
@@ -41,6 +42,50 @@
     <string name="share">Share</string>
     <string name="share_test_results">Share Test Results</string>
 
+    <!-- Strings for BluetoothActivity -->
+    <string name="bluetooth_test">Bluetooth Test</string>
+    <string name="bluetooth_test_info">The Bluetooth Control tests check whether or not the device 
+        can disable and enable Bluetooth properly.\n\nThe Device Communication tests require two
+        devices to pair and exchange messages. The two devices must be:
+        \n\n1. a candidate device implementation running the software build to be tested
+        \n\n2. a separate device implementation already known to be compatible</string>
+
+    <string name="bt_not_available_title">Bluetooth is not available?</string>
+    <string name="bt_not_available_message">If your device is supposed to have Bluetooth, your API implementation is not behaving correctly!</string>
+
+    <string name="bt_control">Bluetooth Control</string>
+    <string name="bt_device_communication">Device Communication</string>
+
+    <string name="bt_toggle_bluetooth">Toggle Bluetooth</string>
+    <string name="bt_toggle_instructions">Disable and enable Bluetooth to successfully complete this test.</string>
+    <string name="bt_enable_bluetooth">Enable Bluetooth</string>
+    <string name="bt_disable_bluetooth">Disable Bluetooth</string>
+    <string name="bt_disabling">Disabling Bluetooth...</string>
+    <string name="bt_disabling_error">Could not disable Bluetooth...</string>
+
+    <string name="bt_secure_server">Secure Server</string>
+    <string name="bt_secure_server_instructions">Start the CTS Verifier on another device, start the Bluetooth test, and choose \"Secure Client\" to complete the test.</string>
+    <string name="bt_insecure_server">Insecure Server</string>
+    <string name="bt_insecure_server_instructions">Start the CTS Verifier on another device, start the Bluetooth test, and choose \"Insecure Client\" to complete the test.</string>
+    <string name="bt_waiting">Waiting for client...</string>
+    <string name="bt_connecting">Connecting...</string>
+    <string name="bt_received_messages">Received Messages</string>
+    <string name="bt_sent_messages">Sent Messages</string>
+    <string name="bt_no_messages">No messages</string>
+    <string name="bt_make_discoverable">Make Discoverable</string>
+
+    <string name="bt_secure_client">Secure Client</string>
+    <string name="bt_insecure_client">Insecure Client</string>
+
+    <string name="bt_device_picker">Device Picker</string>
+    <string name="bt_paired_devices">Paired Devices</string>
+    <string name="bt_new_devices">New Devices</string>
+    <string name="bt_no_devices">No devices</string>
+    <string name="bt_scan">Scan for Devices</string>
+    <string name="bt_scanning">Scanning...</string>
+    <string name="bt_unpair">Device must be unpaired via Bluetooth settings before completing the test.\n\nUnpair the device in settings, make the server discoverable, and rescan to pick this device.</string>
+    <string name="bt_settings">Bluetooth Settings</string>
+
     <!-- Strings for FeatureSummaryActivity -->
     <string name="feature_summary">Hardware/Software Feature Summary</string>
     <string name="feature_summary_info">This is a test for...</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
index c070ec8..b629fc5 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
@@ -26,6 +26,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.widget.Button;
 
 /**
  * {@link Activity}s to handle clicks to the pass and fail buttons of the pass fail buttons layout.
@@ -64,6 +65,8 @@
          * @param messageId for the text shown in the dialog's body area
          */
         void setInfoResources(int titleId, int messageId, int viewId);
+
+        Button getPassButton();
     }
 
     public static class Activity extends android.app.Activity implements PassFailActivity {
@@ -75,6 +78,10 @@
         public void setInfoResources(int titleId, int messageId, int viewId) {
             setInfo(this, titleId, messageId, viewId);
         }
+
+        public Button getPassButton() {
+            return getPassButtonView(this);
+        }
     }
 
     public static class ListActivity extends android.app.ListActivity implements PassFailActivity {
@@ -86,6 +93,10 @@
         public void setInfoResources(int titleId, int messageId, int viewId) {
             setInfo(this, titleId, messageId, viewId);
         }
+
+        public Button getPassButton() {
+            return getPassButtonView(this);
+        }
     }
 
     private static void setPassFailClickListeners(final android.app.Activity activity) {
@@ -185,4 +196,8 @@
 
         activity.finish();
     }
+
+    private static Button getPassButtonView(android.app.Activity activity) {
+        return (Button) activity.findViewById(R.id.pass_button);
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothChatService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothChatService.java
new file mode 100644
index 0000000..05c50e3
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothChatService.java
@@ -0,0 +1,500 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.verifier.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothServerSocket;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.UUID;
+
+/**
+ * This class does all the work for setting up and managing Bluetooth
+ * connections with other devices. It has a thread that listens for
+ * incoming connections, a thread for connecting with a device, and a
+ * thread for performing data transmissions when connected.
+ */
+public class BluetoothChatService {
+    // Message types sent from the BluetoothChatService Handler
+    public static final int MESSAGE_STATE_CHANGE = 1;
+    public static final int MESSAGE_READ = 2;
+    public static final int MESSAGE_WRITE = 3;
+    public static final int MESSAGE_DEVICE_NAME = 4;
+    public static final int MESSAGE_TOAST = 5;
+
+    // Key names received from the BluetoothChatService Handler
+    public static final String DEVICE_NAME = "device_name";
+    public static final String TOAST = "toast";
+
+    // Debugging
+    private static final String TAG = "CtsBluetoothChatService";
+    private static final boolean D = true;
+
+    // Name for the SDP record when creating server socket
+    private static final String NAME_SECURE = "CtsBluetoothChatSecure";
+    private static final String NAME_INSECURE = "CtsBluetoothChatInsecure";
+
+    // Unique UUID for this application
+    private static final UUID MY_UUID_SECURE =
+        UUID.fromString("8591d757-18ee-45e1-9b12-92875d06ba23");
+    private static final UUID MY_UUID_INSECURE =
+        UUID.fromString("301c214f-91a2-43bf-a795-09d1198a81a7");
+
+    // Member fields
+    private final BluetoothAdapter mAdapter;
+    private final Handler mHandler;
+    private AcceptThread mSecureAcceptThread;
+    private AcceptThread mInsecureAcceptThread;
+    private ConnectThread mConnectThread;
+    private ConnectedThread mConnectedThread;
+    private int mState;
+
+    // Constants that indicate the current connection state
+    public static final int STATE_NONE = 0;       // we're doing nothing
+    public static final int STATE_LISTEN = 1;     // now listening for incoming connections
+    public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
+    public static final int STATE_CONNECTED = 3;  // now connected to a remote device
+
+    /**
+     * Constructor. Prepares a new BluetoothChat session.
+     * @param context  The UI Activity Context
+     * @param handler  A Handler to send messages back to the UI Activity
+     */
+    public BluetoothChatService(Context context, Handler handler) {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mState = STATE_NONE;
+        mHandler = handler;
+    }
+
+    /**
+     * Set the current state of the chat connection
+     * @param state  An integer defining the current connection state
+     */
+    private synchronized void setState(int state) {
+        if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
+        mState = state;
+
+        // Give the new state to the Handler so the UI Activity can update
+        mHandler.obtainMessage(MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
+    }
+
+    /**
+     * Return the current connection state. */
+    public synchronized int getState() {
+        return mState;
+    }
+
+    /**
+     * Start the chat service. Specifically start AcceptThread to begin a
+     * session in listening (server) mode. Called by the Activity onResume() */
+    public synchronized void start(boolean secure) {
+        if (D) Log.d(TAG, "start secure: " + secure + UUID.randomUUID() + " - " + UUID.randomUUID());
+
+        // Cancel any thread attempting to make a connection
+        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
+
+        // Cancel any thread currently running a connection
+        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
+
+        setState(STATE_LISTEN);
+
+        // Start the thread to listen on a BluetoothServerSocket
+        if (secure && mSecureAcceptThread == null) {
+            mSecureAcceptThread = new AcceptThread(true);
+            mSecureAcceptThread.start();
+        }
+        if (!secure && mInsecureAcceptThread == null) {
+            mInsecureAcceptThread = new AcceptThread(false);
+            mInsecureAcceptThread.start();
+        }
+    }
+
+    /**
+     * Start the ConnectThread to initiate a connection to a remote device.
+     * @param device  The BluetoothDevice to connect
+     * @param secure Socket Security type - Secure (true) , Insecure (false)
+     */
+    public synchronized void connect(BluetoothDevice device, boolean secure) {
+        if (D) Log.d(TAG, "connect to: " + device);
+
+        // Cancel any thread attempting to make a connection
+        if (mState == STATE_CONNECTING) {
+            if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
+        }
+
+        // Cancel any thread currently running a connection
+        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
+
+        // Start the thread to connect with the given device
+        mConnectThread = new ConnectThread(device, secure);
+        mConnectThread.start();
+        setState(STATE_CONNECTING);
+    }
+
+    /**
+     * Start the ConnectedThread to begin managing a Bluetooth connection
+     * @param socket  The BluetoothSocket on which the connection was made
+     * @param device  The BluetoothDevice that has been connected
+     */
+    public synchronized void connected(BluetoothSocket socket, BluetoothDevice
+            device, final String socketType) {
+        if (D) Log.d(TAG, "connected, Socket Type: " + socketType);
+
+        // Cancel the thread that completed the connection
+        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
+
+        // Cancel any thread currently running a connection
+        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
+
+        // Cancel the accept thread because we only want to connect to one device
+        if (mSecureAcceptThread != null) {
+            mSecureAcceptThread.cancel();
+            mSecureAcceptThread = null;
+        }
+        if (mInsecureAcceptThread != null) {
+            mInsecureAcceptThread.cancel();
+            mInsecureAcceptThread = null;
+        }
+
+        // Start the thread to manage the connection and perform transmissions
+        mConnectedThread = new ConnectedThread(socket, socketType);
+        mConnectedThread.start();
+
+        // Send the name of the connected device back to the UI Activity
+        Message msg = mHandler.obtainMessage(MESSAGE_DEVICE_NAME);
+        Bundle bundle = new Bundle();
+        bundle.putString(DEVICE_NAME, device.getName());
+        msg.setData(bundle);
+        mHandler.sendMessage(msg);
+
+        setState(STATE_CONNECTED);
+    }
+
+    /**
+     * Stop all threads
+     */
+    public synchronized void stop() {
+        if (D) Log.d(TAG, "stop");
+
+        if (mConnectThread != null) {
+            mConnectThread.cancel();
+            mConnectThread = null;
+        }
+
+        if (mConnectedThread != null) {
+            mConnectedThread.cancel();
+            mConnectedThread = null;
+        }
+
+        if (mSecureAcceptThread != null) {
+            mSecureAcceptThread.cancel();
+            mSecureAcceptThread = null;
+        }
+
+        if (mInsecureAcceptThread != null) {
+            mInsecureAcceptThread.cancel();
+            mInsecureAcceptThread = null;
+        }
+        setState(STATE_NONE);
+    }
+
+    /**
+     * Write to the ConnectedThread in an unsynchronized manner
+     * @param out The bytes to write
+     * @see ConnectedThread#write(byte[])
+     */
+    public void write(byte[] out) {
+        // Create temporary object
+        ConnectedThread r;
+        // Synchronize a copy of the ConnectedThread
+        synchronized (this) {
+            if (mState != STATE_CONNECTED) return;
+            r = mConnectedThread;
+        }
+        // Perform the write unsynchronized
+        r.write(out);
+    }
+
+    /**
+     * Indicate that the connection attempt failed and notify the UI Activity.
+     */
+    private void connectionFailed() {
+        // Send a failure message back to the Activity
+        Message msg = mHandler.obtainMessage(MESSAGE_TOAST);
+        Bundle bundle = new Bundle();
+        bundle.putString(TOAST, "Unable to connect device");
+        msg.setData(bundle);
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * Indicate that the connection was lost and notify the UI Activity.
+     */
+    private void connectionLost() {
+        // Send a failure message back to the Activity
+        Message msg = mHandler.obtainMessage(MESSAGE_TOAST);
+        Bundle bundle = new Bundle();
+        bundle.putString(TOAST, "Device connection was lost");
+        msg.setData(bundle);
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * This thread runs while listening for incoming connections. It behaves
+     * like a server-side client. It runs until a connection is accepted
+     * (or until cancelled).
+     */
+    private class AcceptThread extends Thread {
+        // The local server socket
+        private final BluetoothServerSocket mmServerSocket;
+        private String mSocketType;
+
+        public AcceptThread(boolean secure) {
+            BluetoothServerSocket tmp = null;
+            mSocketType = secure ? "Secure" : "Insecure";
+
+            // Create a new listening server socket
+            try {
+                if (secure) {
+                    tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE,
+                        MY_UUID_SECURE);
+                } else {
+                    tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(
+                            NAME_INSECURE, MY_UUID_INSECURE);
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Socket Type: " + mSocketType + " listen() failed", e);
+            }
+            mmServerSocket = tmp;
+        }
+
+        public void run() {
+            if (D) Log.d(TAG, "Socket Type: " + mSocketType +
+                    " BEGIN mAcceptThread" + this);
+            setName("AcceptThread" + mSocketType);
+
+            BluetoothSocket socket = null;
+
+            // Listen to the server socket if we're not connected
+            while (mState != STATE_CONNECTED) {
+                try {
+                    // This is a blocking call and will only return on a
+                    // successful connection or an exception
+                    socket = mmServerSocket.accept();
+                } catch (IOException e) {
+                    Log.e(TAG, "Socket Type: " + mSocketType + " accept() failed", e);
+                    break;
+                }
+
+                // If a connection was accepted
+                if (socket != null) {
+                    synchronized (BluetoothChatService.this) {
+                        switch (mState) {
+                        case STATE_LISTEN:
+                        case STATE_CONNECTING:
+                            // Situation normal. Start the connected thread.
+                            connected(socket, socket.getRemoteDevice(),
+                                    mSocketType);
+                            break;
+                        case STATE_NONE:
+                        case STATE_CONNECTED:
+                            // Either not ready or already connected. Terminate new socket.
+                            try {
+                                socket.close();
+                            } catch (IOException e) {
+                                Log.e(TAG, "Could not close unwanted socket", e);
+                            }
+                            break;
+                        }
+                    }
+                } else {
+                    Log.i(TAG, "Got null socket");
+                }
+            }
+            if (D) Log.i(TAG, "END mAcceptThread, socket Type: " + mSocketType);
+
+        }
+
+        public void cancel() {
+            if (D) Log.d(TAG, "Socket Type" + mSocketType + "cancel " + this);
+            try {
+                mmServerSocket.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Socket Type" + mSocketType + "close() of server failed", e);
+            }
+        }
+    }
+
+
+    /**
+     * This thread runs while attempting to make an outgoing connection
+     * with a device. It runs straight through; the connection either
+     * succeeds or fails.
+     */
+    private class ConnectThread extends Thread {
+        private final BluetoothSocket mmSocket;
+        private final BluetoothDevice mmDevice;
+        private String mSocketType;
+
+        public ConnectThread(BluetoothDevice device, boolean secure) {
+            mmDevice = device;
+            BluetoothSocket tmp = null;
+            mSocketType = secure ? "Secure" : "Insecure";
+
+            // Get a BluetoothSocket for a connection with the
+            // given BluetoothDevice
+            try {
+                if (secure) {
+                    tmp = device.createRfcommSocketToServiceRecord(
+                            MY_UUID_SECURE);
+                } else {
+                    tmp = device.createInsecureRfcommSocketToServiceRecord(
+                            MY_UUID_INSECURE);
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
+            }
+            mmSocket = tmp;
+        }
+
+        public void run() {
+            Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType);
+            setName("ConnectThread" + mSocketType);
+
+            // Always cancel discovery because it will slow down a connection
+            mAdapter.cancelDiscovery();
+
+            // Make a connection to the BluetoothSocket
+            try {
+                // This is a blocking call and will only return on a
+                // successful connection or an exception
+                mmSocket.connect();
+            } catch (IOException e) {
+                Log.e(TAG, "connect() failed ", e);
+                // Close the socket
+                try {
+                    mmSocket.close();
+                } catch (IOException e2) {
+                    Log.e(TAG, "unable to close() " + mSocketType +
+                            " socket during connection failure", e2);
+                }
+                connectionFailed();
+                return;
+            }
+
+            // Reset the ConnectThread because we're done
+            synchronized (BluetoothChatService.this) {
+                mConnectThread = null;
+            }
+
+            // Start the connected thread
+            connected(mmSocket, mmDevice, mSocketType);
+        }
+
+        public void cancel() {
+            try {
+                mmSocket.close();
+            } catch (IOException e) {
+                Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e);
+            }
+        }
+    }
+
+    /**
+     * This thread runs during a connection with a remote device.
+     * It handles all incoming and outgoing transmissions.
+     */
+    private class ConnectedThread extends Thread {
+        private final BluetoothSocket mmSocket;
+        private final InputStream mmInStream;
+        private final OutputStream mmOutStream;
+
+        public ConnectedThread(BluetoothSocket socket, String socketType) {
+            Log.d(TAG, "create ConnectedThread: " + socketType);
+            mmSocket = socket;
+            InputStream tmpIn = null;
+            OutputStream tmpOut = null;
+
+            // Get the BluetoothSocket input and output streams
+            try {
+                tmpIn = socket.getInputStream();
+                tmpOut = socket.getOutputStream();
+            } catch (IOException e) {
+                Log.e(TAG, "temp sockets not created", e);
+            }
+
+            mmInStream = tmpIn;
+            mmOutStream = tmpOut;
+        }
+
+        public void run() {
+            Log.i(TAG, "BEGIN mConnectedThread");
+            byte[] buffer = new byte[1024];
+            int bytes;
+
+            // Keep listening to the InputStream while connected
+            while (true) {
+                try {
+                    // Read from the InputStream
+                    bytes = mmInStream.read(buffer);
+
+                    // Send the obtained bytes to the UI Activity
+                    mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
+                            .sendToTarget();
+                } catch (IOException e) {
+                    Log.e(TAG, "disconnected", e);
+                    connectionLost();
+                    break;
+                }
+            }
+        }
+
+        /**
+         * Write to the connected OutStream.
+         * @param buffer  The bytes to write
+         */
+        public void write(byte[] buffer) {
+            try {
+                mmOutStream.write(buffer);
+                mmOutStream.flush();
+
+                // Share the sent message back to the UI Activity
+                mHandler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer)
+                        .sendToTarget();
+            } catch (IOException e) {
+                Log.e(TAG, "Exception during write", e);
+            }
+        }
+
+        public void cancel() {
+            try {
+                mmSocket.close();
+            } catch (IOException e) {
+                Log.e(TAG, "close() of connect socket failed", e);
+            }
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
new file mode 100644
index 0000000..a6f9919
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
@@ -0,0 +1,512 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.verifier.bluetooth;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestResult;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BluetoothTestActivity extends PassFailButtons.ListActivity {
+
+    public static final int TEST_BLUETOOTH_TOGGLE = 0;
+    public static final int TEST_SECURE_SERVER = 1;
+    public static final int TEST_INSECURE_SERVER = 2;
+    public static final int TEST_SECURE_CLIENT = 3;
+    public static final int TEST_INSECURE_CLIENT = 4;
+
+    private static final int START_TOGGLE_BLUETOOTH_TEST_REQUEST = 1;
+    private static final int START_SECURE_SERVER_REQUEST = 2;
+    private static final int START_INSECURE_SERVER_REQUEST = 3;
+    private static final int START_SECURE_PICK_SERVER_REQUEST = 4;
+    private static final int START_INSECURE_PICK_SERVER_REQUEST = 5;
+    private static final int START_SECURE_CLIENT_REQUEST = 6;
+    private static final int START_INSECURE_CLIENT_REQUEST = 7;
+
+    private TestListItem mBluetoothToggleTest;
+    private TestListItem mSecureServerTest;
+    private TestListItem mInsecureServerTest;
+    private TestListItem mSecureClientTest;
+    private TestListItem mInsecureClientTest;
+
+    private static final String TABLE_NAME = "results";
+    private static final String _ID = "_id";
+    private static final String COLUMN_TEST_ID = "test_id";
+    private static final String COLUMN_TEST_RESULT = "test_result";
+    private static final String[] ALL_COLUMNS = {
+          _ID,
+          COLUMN_TEST_ID,
+          COLUMN_TEST_RESULT,
+    };
+
+    private TestListAdapter mAdapter;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.bt_main);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.bluetooth_test, R.string.bluetooth_test_info, -1);
+
+        mBluetoothToggleTest = TestListItem.newTest(R.string.bt_toggle_bluetooth,
+                TEST_BLUETOOTH_TOGGLE);
+        mSecureServerTest = TestListItem.newTest(R.string.bt_secure_server,
+                TEST_SECURE_SERVER);
+        mInsecureServerTest = TestListItem.newTest(R.string.bt_insecure_server,
+                TEST_INSECURE_SERVER);
+        mSecureClientTest = TestListItem.newTest(R.string.bt_secure_client,
+                TEST_SECURE_CLIENT);
+        mInsecureClientTest = TestListItem.newTest(R.string.bt_insecure_client,
+                TEST_INSECURE_CLIENT);
+
+        mAdapter = new TestListAdapter(this);
+        mAdapter.add(TestListItem.newCategory(R.string.bt_control));
+        mAdapter.add(mBluetoothToggleTest);
+
+        mAdapter.add(TestListItem.newCategory(R.string.bt_device_communication));
+        mAdapter.add(mSecureServerTest);
+        mAdapter.add(mInsecureServerTest);
+        mAdapter.add(mSecureClientTest);
+        mAdapter.add(mInsecureClientTest);
+
+        setListAdapter(mAdapter);
+        refreshTestResults();
+
+        if (BluetoothAdapter.getDefaultAdapter() == null) {
+            showNoBluetoothDialog();
+        }
+    }
+
+    private void refreshTestResults() {
+        new RefreshTask().execute();
+    }
+
+    private void showNoBluetoothDialog() {
+        new AlertDialog.Builder(this)
+        .setIcon(android.R.drawable.ic_dialog_alert)
+        .setTitle(R.string.bt_not_available_title)
+        .setMessage(R.string.bt_not_available_message)
+        .setCancelable(false)
+        .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                finish();
+            }
+        })
+        .show();
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        super.onListItemClick(l, v, position, id);
+        TestListItem testItem = (TestListItem) l.getItemAtPosition(position);
+        switch (testItem.mId) {
+            case TEST_BLUETOOTH_TOGGLE:
+                startToggleBluetoothActivity();
+                break;
+
+            case TEST_SECURE_SERVER:
+                startServerActivity(true);
+                break;
+
+            case TEST_INSECURE_SERVER:
+                startServerActivity(false);
+                break;
+
+            case TEST_SECURE_CLIENT:
+                startDevicePickerActivity(true);
+                break;
+
+            case TEST_INSECURE_CLIENT:
+                startDevicePickerActivity(false);
+                break;
+        }
+    }
+
+    private void startToggleBluetoothActivity() {
+        Intent intent = new Intent(this, BluetoothToggleActivity.class);
+        startActivityForResult(intent, START_TOGGLE_BLUETOOTH_TEST_REQUEST);
+    }
+
+    private void startServerActivity(boolean secure) {
+        Intent intent = new Intent(this, MessageTestActivity.class)
+                .putExtra(MessageTestActivity.EXTRA_SECURE, secure);
+        startActivityForResult(intent, secure
+                ? START_SECURE_SERVER_REQUEST
+                : START_INSECURE_SERVER_REQUEST);
+    }
+
+    private void startDevicePickerActivity(boolean secure) {
+        Intent intent = new Intent(this, DevicePickerActivity.class);
+        startActivityForResult(intent, secure
+                ? START_SECURE_PICK_SERVER_REQUEST
+                : START_INSECURE_PICK_SERVER_REQUEST);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        switch (requestCode) {
+            case START_TOGGLE_BLUETOOTH_TEST_REQUEST:
+                handleEnableBluetoothResult(resultCode, data);
+                break;
+
+            case START_SECURE_SERVER_REQUEST:
+                handleServerResult(resultCode, data, true);
+                break;
+
+            case START_INSECURE_SERVER_REQUEST:
+                handleServerResult(resultCode, data, false);
+                break;
+
+            case START_SECURE_PICK_SERVER_REQUEST:
+                handleDevicePickerResult(resultCode, data, true);
+                break;
+
+            case START_INSECURE_PICK_SERVER_REQUEST:
+                handleDevicePickerResult(resultCode, data, false);
+                break;
+
+            case START_SECURE_CLIENT_REQUEST:
+                handleClientResult(resultCode, data, true);
+                break;
+
+            case START_INSECURE_CLIENT_REQUEST:
+                handleClientResult(resultCode, data, false);
+                break;
+        }
+    }
+
+    private void handleEnableBluetoothResult(int resultCode, Intent data) {
+        if (data != null) {
+            TestResult result = TestResult.fromActivityResult(resultCode, data);
+            mBluetoothToggleTest.setResult(result.getResult());
+            updateTest(mBluetoothToggleTest);
+        }
+    }
+
+    private void updateTest(TestListItem item) {
+        new UpdateTask().execute(item.mId, item.mResult);
+    }
+
+    private void handleServerResult(int resultCode, Intent data, boolean secure) {
+        if (data != null) {
+            TestResult result = TestResult.fromActivityResult(resultCode, data);
+            TestListItem test = secure ? mSecureServerTest : mInsecureServerTest;
+            test.setResult(result.getResult());
+            updateTest(test);
+        }
+    }
+
+    private void handleDevicePickerResult(int resultCode, Intent data, boolean secure) {
+        if (resultCode == RESULT_OK) {
+            String address = data.getStringExtra(DevicePickerActivity.EXTRA_DEVICE_ADDRESS);
+            startClientActivity(address, secure);
+        }
+    }
+
+    private void startClientActivity(String address, boolean secure) {
+        Intent intent = new Intent(this, MessageTestActivity.class)
+                .putExtra(MessageTestActivity.EXTRA_DEVICE_ADDRESS, address)
+                .putExtra(MessageTestActivity.EXTRA_SECURE, secure);
+        startActivityForResult(intent, secure
+                ? START_SECURE_CLIENT_REQUEST
+                : START_INSECURE_CLIENT_REQUEST);
+    }
+
+    private void handleClientResult(int resultCode, Intent data, boolean secure) {
+        if (data != null) {
+            TestResult result = TestResult.fromActivityResult(resultCode, data);
+            TestListItem test = secure ? mSecureClientTest : mInsecureClientTest;
+            updateTest(test);
+        }
+    }
+
+    private class UpdateTask extends AsyncTask<Integer, Void, Void> {
+
+        @Override
+        protected Void doInBackground(Integer[] resultPairs) {
+            TestResultsHelper openHelper = new TestResultsHelper(BluetoothTestActivity.this);
+            SQLiteDatabase db = openHelper.getWritableDatabase();
+
+            int testId = resultPairs[0];
+            int testResult = resultPairs[1];
+
+            ContentValues values = new ContentValues(2);
+            values.put(COLUMN_TEST_ID, testId);
+            values.put(COLUMN_TEST_RESULT, testResult);
+
+            try {
+                if (0 == db.update(TABLE_NAME, values, COLUMN_TEST_ID + " = ?",
+                        new String[] {Integer.toString(testId)})) {
+                    db.insert(TABLE_NAME, null, values);
+                }
+            } finally {
+                if (db != null) {
+                    db.close();
+                }
+            }
+
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Void result) {
+            super.onPostExecute(result);
+            refreshTestResults();
+        }
+    }
+
+    private class RefreshTask extends AsyncTask<Void, Void, Map<Integer, Integer>> {
+
+        @Override
+        protected Map<Integer, Integer> doInBackground(Void... params) {
+            Map<Integer, Integer> results = new HashMap<Integer, Integer>();
+            TestResultsHelper openHelper = new TestResultsHelper(BluetoothTestActivity.this);
+            SQLiteDatabase db = openHelper.getReadableDatabase();
+            Cursor cursor = null;
+            try {
+                cursor = db.query(TABLE_NAME, ALL_COLUMNS, null, null, null, null, null, null);
+                while (cursor.moveToNext()) {
+                    int testId = cursor.getInt(1);
+                    int testResult = cursor.getInt(2);
+                    results.put(testId, testResult);
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+                if (db != null) {
+                    db.close();
+                }
+            }
+            return results;
+        }
+
+        @Override
+        protected void onPostExecute(Map<Integer, Integer> results) {
+            super.onPostExecute(results);
+            for (Integer testId : results.keySet()) {
+                TestListItem item = mAdapter.getTest(testId);
+                if (item != null) {
+                    item.setResult(results.get(testId));
+                }
+            }
+            mAdapter.notifyDataSetChanged();
+        }
+    }
+
+    static class TestListItem {
+
+        static final int NUM_VIEW_TYPES = 2;
+
+        static final int VIEW_TYPE_CATEGORY = 0;
+
+        static final int VIEW_TYPE_TEST = 1;
+
+        final int mViewType;
+
+        final int mTitle;
+
+        final int mId;
+
+        int mResult;
+
+        static TestListItem newTest(int title, int id) {
+            return new TestListItem(VIEW_TYPE_TEST, title, id);
+        }
+
+        static TestListItem newCategory(int title) {
+            return new TestListItem(VIEW_TYPE_CATEGORY, title, -1);
+        }
+
+        private TestListItem(int viewType, int title, int id) {
+            this.mViewType = viewType;
+            this.mTitle = title;
+            this.mId = id;
+        }
+
+        public boolean isTest() {
+            return mViewType == VIEW_TYPE_TEST;
+        }
+
+        public void setResult(int result) {
+            mResult = result;
+        }
+    }
+
+    static class TestListAdapter extends BaseAdapter {
+
+        private static final int PADDING = 10;
+
+        private final List<TestListItem> mItems = new ArrayList<TestListItem>();
+
+        private final Map<Integer, TestListItem> mTestsById = new HashMap<Integer, TestListItem>();
+
+        private final LayoutInflater mInflater;
+
+        public TestListAdapter(Context context) {
+            mInflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
+        }
+
+        public void add(TestListItem item) {
+            mItems.add(item);
+            if (item.isTest()) {
+                mTestsById.put(item.mId, item);
+            }
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            int backgroundResource = 0;
+            int iconResource = 0;
+
+            TestListItem item = getItem(position);
+
+            TextView textView = null;
+            if (convertView == null) {
+                int layout = getLayout(position);
+                textView = (TextView) mInflater.inflate(layout, parent, false);
+            } else {
+                textView = (TextView) convertView;
+            }
+
+            textView.setText(item.mTitle);
+
+            if (item.isTest()) {
+                switch (item.mResult) {
+                    case TestResult.TEST_RESULT_PASSED:
+                        backgroundResource = R.drawable.test_pass_gradient;
+                        iconResource = R.drawable.fs_good;
+                        break;
+
+                    case TestResult.TEST_RESULT_FAILED:
+                        backgroundResource = R.drawable.test_fail_gradient;
+                        iconResource = R.drawable.fs_error;
+                        break;
+
+                    case TestResult.TEST_RESULT_NOT_EXECUTED:
+                        break;
+
+                    default:
+                        throw new IllegalArgumentException("Unknown test result: " + item.mResult);
+                }
+
+                textView.setBackgroundResource(backgroundResource);
+                textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, iconResource, 0);
+                textView.setPadding(PADDING, 0, PADDING, 0);
+                textView.setCompoundDrawablePadding(PADDING);
+            }
+
+            return textView;
+        }
+
+        private int getLayout(int position) {
+            int viewType = getItemViewType(position);
+            switch (viewType) {
+                case TestListItem.VIEW_TYPE_CATEGORY:
+                    return R.layout.test_category_row;
+                case TestListItem.VIEW_TYPE_TEST:
+                    return android.R.layout.simple_list_item_1;
+                default:
+                    throw new IllegalArgumentException("Illegal view type: " + viewType);
+
+            }
+        }
+
+        public TestListItem getTest(int id) {
+            return mTestsById.get(id);
+        }
+
+        @Override
+        public int getCount() {
+            return mItems.size();
+        }
+
+        @Override
+        public TestListItem getItem(int position) {
+            return mItems.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return getItem(position).mId;
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return TestListItem.NUM_VIEW_TYPES;
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            return getItem(position).mViewType;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return getItemViewType(position) != TestListItem.VIEW_TYPE_CATEGORY;
+        }
+    }
+
+    class TestResultsHelper extends SQLiteOpenHelper {
+
+        private static final String DATABASE_NAME = "bluetooth_results.db";
+
+        private static final int DATABASE_VERSION = 1;
+
+        TestResultsHelper(Context context) {
+            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE_NAME + " ("
+                    + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+                    + COLUMN_TEST_ID + " INTEGER, "
+                    + COLUMN_TEST_RESULT + " INTEGER DEFAULT 0);");
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+            onCreate(db);
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothToggleActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothToggleActivity.java
new file mode 100644
index 0000000..7106e7b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothToggleActivity.java
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.verifier.bluetooth;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ToggleButton;
+
+/**
+ * Activity for testing that Bluetooth can be disabled and enabled properly. The activity shows
+ * a button that toggles Bluetooth by disabling it via {@link BluetoothAdapter#disable()} and
+ * enabling it via the Intent action {@link BluetoothAdapter#ACTION_REQUEST_ENABLE}.
+ */
+public class BluetoothToggleActivity extends PassFailButtons.Activity {
+
+    private static final String TAG = BluetoothToggleActivity.class.getName();
+
+    private static final int START_ENABLE_BLUETOOTH_REQUEST = 1;
+
+    private BluetoothAdapter mBluetoothAdapter;
+
+    private BluetoothBroadcastReceiver mReceiver;
+
+    private ProgressDialog mDisablingDialog;
+
+    private ToggleButton mToggleButton;
+
+    private int mNumDisabledTimes = 0;
+
+    private int mNumEnabledTimes = 0;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.bt_toggle);
+        setPassFailButtonClickListeners();
+
+        mReceiver = new BluetoothBroadcastReceiver();
+        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+        registerReceiver(mReceiver, filter);
+
+        mDisablingDialog = new ProgressDialog(this);
+        mDisablingDialog.setMessage(getString(R.string.bt_disabling));
+
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        getPassButton().setEnabled(false);
+
+        mToggleButton = (ToggleButton) findViewById(R.id.bt_toggle_button);
+        mToggleButton.setChecked(mBluetoothAdapter.isEnabled());
+        mToggleButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mToggleButton.isChecked()) {
+                    enableBluetooth();
+                } else {
+                    disableBluetooth();
+                }
+            }
+        });
+    }
+
+    private void enableBluetooth() {
+        mDisablingDialog.hide();
+        mToggleButton.setEnabled(false);
+        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+        startActivityForResult(intent, START_ENABLE_BLUETOOTH_REQUEST);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        switch (requestCode) {
+            case START_ENABLE_BLUETOOTH_REQUEST:
+                boolean enabledBluetooth = RESULT_OK == resultCode;
+                mToggleButton.setChecked(enabledBluetooth);
+                mToggleButton.setEnabled(true);
+                break;
+        }
+    }
+
+    private void disableBluetooth() {
+        mDisablingDialog.show();
+        mToggleButton.setEnabled(false);
+        if (!mBluetoothAdapter.disable()) {
+            mDisablingDialog.hide();
+            mToggleButton.setEnabled(true);
+            new AlertDialog.Builder(this)
+                .setIcon(android.R.drawable.ic_dialog_alert)
+                .setMessage(R.string.bt_disabling_error)
+                .setPositiveButton(android.R.string.ok, null)
+                .show();
+        }
+    }
+
+    class BluetoothBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            int previousState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, -1);
+            int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
+            Log.i(TAG, "Previous state: " + previousState + " New state: " + newState);
+
+            if (BluetoothAdapter.STATE_OFF == newState
+                    && (BluetoothAdapter.STATE_ON == previousState
+                            || BluetoothAdapter.STATE_TURNING_OFF == previousState)) {
+                mNumDisabledTimes++;
+            }
+
+            if (BluetoothAdapter.STATE_ON == newState
+                    && (BluetoothAdapter.STATE_OFF == previousState
+                            || BluetoothAdapter.STATE_TURNING_ON == previousState)) {
+                mNumEnabledTimes++;
+            }
+
+            if (BluetoothAdapter.STATE_OFF == newState) {
+                mDisablingDialog.hide();
+                mToggleButton.setEnabled(true);
+            }
+
+            mToggleButton.setChecked(mBluetoothAdapter.isEnabled());
+            getPassButton().setEnabled(mNumDisabledTimes > 0 &&  mNumEnabledTimes > 0);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mReceiver);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/DevicePickerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/DevicePickerActivity.java
new file mode 100644
index 0000000..be71f66
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/DevicePickerActivity.java
@@ -0,0 +1,219 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.verifier.bluetooth;
+
+import com.android.cts.verifier.R;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.View;
+import android.view.Window;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.AdapterView.OnItemClickListener;
+
+import java.util.Set;
+
+/**
+ * {@link Activity} that shows a list of paired and new devices and returns the device selected
+ * by the user. When the user selects a paired device, it forwards them to the Bluetooth settings
+ * page, so that they can unpair it for the test.
+ */
+public class DevicePickerActivity extends Activity {
+
+    public static final String EXTRA_DEVICE_ADDRESS = "deviceAddress";
+
+    private static final int ENABLE_BLUETOOTH_REQUEST = 1;
+
+    private BluetoothAdapter mBluetoothAdapter;
+
+    private DiscoveryReceiver mReceiver;
+
+    private ArrayAdapter<Device> mNewDevicesAdapter;
+
+    private ArrayAdapter<Device> mPairedDevicesAdapter;
+
+    private TextView mEmptyNewView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        setContentView(R.layout.bt_device_picker);
+
+        mPairedDevicesAdapter = new ArrayAdapter<Device>(this, R.layout.bt_device_name);
+        ListView pairedDevicesListView = (ListView) findViewById(R.id.bt_paired_devices);
+        pairedDevicesListView.setAdapter(mPairedDevicesAdapter);
+        pairedDevicesListView.setOnItemClickListener(new PairedDeviceClickListener());
+
+        View emptyPairedView = findViewById(R.id.bt_empty_paired_devices);
+        pairedDevicesListView.setEmptyView(emptyPairedView);
+
+        mNewDevicesAdapter = new ArrayAdapter<Device>(this, R.layout.bt_device_name);
+        ListView newDevicesListView = (ListView) findViewById(R.id.bt_new_devices);
+        newDevicesListView.setAdapter(mNewDevicesAdapter);
+        newDevicesListView.setOnItemClickListener(new NewDeviceClickListener());
+
+        mEmptyNewView = (TextView) findViewById(R.id.bt_empty_new_devices);
+        newDevicesListView.setEmptyView(mEmptyNewView);
+
+        mReceiver = new DiscoveryReceiver();
+        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
+        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
+        filter.addAction(BluetoothDevice.ACTION_FOUND);
+        registerReceiver(mReceiver, filter);
+
+        Button scanButton = (Button) findViewById(R.id.bt_scan_button);
+        scanButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                scan();
+            }
+        });
+
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (mBluetoothAdapter.isEnabled()) {
+            scan();
+        } else {
+            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+            startActivityForResult(intent, ENABLE_BLUETOOTH_REQUEST);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == ENABLE_BLUETOOTH_REQUEST) {
+            if (resultCode == RESULT_OK) {
+                scan();
+            } else {
+                setResult(RESULT_CANCELED);
+                finish();
+            }
+        }
+    }
+
+    private void scan() {
+        populatePairedDevices();
+        mNewDevicesAdapter.clear();
+        if (mBluetoothAdapter.isDiscovering()) {
+            mBluetoothAdapter.cancelDiscovery();
+        }
+        mBluetoothAdapter.startDiscovery();
+    }
+
+    private void populatePairedDevices() {
+        mPairedDevicesAdapter.clear();
+        Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
+        for (BluetoothDevice device : pairedDevices) {
+            mPairedDevicesAdapter.add(Device.fromBluetoothDevice(device));
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mBluetoothAdapter != null) {
+            mBluetoothAdapter.cancelDiscovery();
+        }
+        unregisterReceiver(mReceiver);
+    }
+
+    class NewDeviceClickListener implements OnItemClickListener {
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            Intent data = new Intent();
+            Device device = (Device) parent.getItemAtPosition(position);
+            data.putExtra(EXTRA_DEVICE_ADDRESS, device.mAddress);
+            setResult(RESULT_OK, data);
+            finish();
+        }
+    }
+
+    class PairedDeviceClickListener implements OnItemClickListener {
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            new AlertDialog.Builder(DevicePickerActivity.this)
+                .setIcon(android.R.drawable.ic_dialog_alert)
+                .setMessage(R.string.bt_unpair)
+                .setPositiveButton(R.string.bt_settings, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        if (mBluetoothAdapter != null) {
+                            mBluetoothAdapter.cancelDiscovery();
+                        }
+                        Intent intent = new Intent();
+                        intent.setAction(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS);
+                        startActivity(intent);
+                    }
+                })
+                .show();
+        }
+    }
+
+    class DiscoveryReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) {
+                mEmptyNewView.setText(R.string.bt_scanning);
+                setProgressBarIndeterminateVisibility(true);
+            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) {
+                mEmptyNewView.setText(R.string.bt_no_devices);
+                setProgressBarIndeterminateVisibility(false);
+            } else if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) {
+                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
+                    mNewDevicesAdapter.add(Device.fromBluetoothDevice(device));
+                }
+            }
+        }
+    }
+
+    static class Device {
+
+        String mName;
+
+        String mAddress;
+
+        Device(String name, String address) {
+            mName = name;
+            mAddress = address;
+        }
+
+        @Override
+        public String toString() {
+            return mName + "\n" + mAddress;
+        }
+
+        static Device fromBluetoothDevice(BluetoothDevice device) {
+            return new Device(device.getName() != null ? device.getName() : "",
+                    device.getAddress());
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/MessageTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/MessageTestActivity.java
new file mode 100644
index 0000000..aabe00c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/MessageTestActivity.java
@@ -0,0 +1,292 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.verifier.bluetooth;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.View;
+import android.view.Window;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class MessageTestActivity extends PassFailButtons.Activity {
+
+    static final String EXTRA_DEVICE_ADDRESS = "deviceAddress";
+    static final String EXTRA_SECURE = "secure";
+
+    private static final int ENABLE_BLUETOOTH_REQUEST = 1;
+
+    private static final String MESSAGE_DELIMITER = "\n";
+    private static final Pattern MESSAGE_PATTERN = Pattern.compile("Message (\\d+) to .*");
+
+    private BluetoothAdapter mBluetoothAdapter;
+    private BluetoothChatService mChatService;
+
+    private ArrayAdapter<String> mReceivedMessagesAdapter;
+    private ArrayAdapter<String> mSentMessagesAdapter;
+
+    private ListView mReceivedMessages;
+    private ListView mSentMessages;
+
+    private TextView mEmptyReceivedView;
+    private TextView mEmptySentView;
+
+    private AlertDialog mInstructionsDialog;
+
+    private String mDeviceAddress;
+    private boolean mSecure;
+    private boolean mServer;
+
+    private String mRemoteDeviceName = "";
+    private StringBuilder mMessageBuffer = new StringBuilder();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        setContentView(R.layout.bt_messages);
+        setPassFailButtonClickListeners();
+
+        mDeviceAddress = getIntent().getStringExtra(EXTRA_DEVICE_ADDRESS);
+        mSecure = getIntent().getBooleanExtra(EXTRA_SECURE, true);
+        mServer = mDeviceAddress == null || mDeviceAddress.isEmpty();
+        if (mServer) {
+            setTitle(mSecure ? R.string.bt_secure_server : R.string.bt_insecure_server);
+        } else {
+            setTitle(mSecure ? R.string.bt_secure_client : R.string.bt_insecure_client);
+        }
+
+        mReceivedMessages = (ListView) findViewById(R.id.bt_received_messages);
+        mReceivedMessagesAdapter = new ArrayAdapter<String>(this, R.layout.bt_message_row);
+        mReceivedMessages.setAdapter(mReceivedMessagesAdapter);
+
+        mSentMessages = (ListView) findViewById(R.id.bt_sent_messages);
+        mSentMessagesAdapter = new ArrayAdapter<String>(this, R.layout.bt_message_row);
+        mSentMessages.setAdapter(mSentMessagesAdapter);
+
+        mEmptyReceivedView = (TextView) findViewById(R.id.bt_empty_received_messages);
+        mReceivedMessages.setEmptyView(mEmptyReceivedView);
+
+        mEmptySentView = (TextView) findViewById(R.id.bt_empty_sent_messages);
+        mSentMessages.setEmptyView(mEmptySentView);
+
+        setEmptyViewText(R.string.bt_no_messages);
+
+        Button makeDiscoverableButton = (Button) findViewById(R.id.bt_make_discoverable_button);
+        makeDiscoverableButton.setVisibility(mServer ? View.VISIBLE : View.GONE);
+        makeDiscoverableButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                makeDiscoverable();
+            }
+        });
+
+        getPassButton().setEnabled(false);
+
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (mBluetoothAdapter.isEnabled()) {
+            startChatService();
+        } else {
+            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+            startActivityForResult(intent, ENABLE_BLUETOOTH_REQUEST);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == ENABLE_BLUETOOTH_REQUEST) {
+            if (resultCode == RESULT_OK) {
+                startChatService();
+            } else {
+                setResult(RESULT_CANCELED);
+                finish();
+            }
+        }
+    }
+
+    private void startChatService() {
+        mChatService = new BluetoothChatService(this, new ChatHandler());
+        if (mServer) {
+            mChatService.start(mSecure);
+        } else {
+            BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mDeviceAddress);
+            mChatService.connect(device, mSecure);
+        }
+    }
+
+    private void makeDiscoverable() {
+        if (mBluetoothAdapter.getScanMode() !=
+                BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
+            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
+            intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 30);
+            startActivity(intent);
+        }
+    }
+
+    private class ChatHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            super.handleMessage(msg);
+            switch (msg.what) {
+                case BluetoothChatService.MESSAGE_STATE_CHANGE:
+                    handleStateChange(msg);
+                    break;
+                case BluetoothChatService.MESSAGE_READ:
+                    handleMessageRead(msg);
+                    break;
+                case BluetoothChatService.MESSAGE_WRITE:
+                    handleMessageWrite(msg);
+                    break;
+                case BluetoothChatService.MESSAGE_DEVICE_NAME:
+                    handleDeviceName(msg);
+                    break;
+                case BluetoothChatService.MESSAGE_TOAST:
+                    handleToast(msg);
+                    break;
+            }
+        }
+    }
+
+    private void handleStateChange(Message msg) {
+        int state = msg.arg1;
+        switch (state) {
+            case BluetoothChatService.STATE_LISTEN:
+                setEmptyViewText(R.string.bt_waiting);
+                setProgressBarIndeterminateVisibility(true);
+                showInstructionsDialog();
+                break;
+
+            case BluetoothChatService.STATE_CONNECTING:
+                setEmptyViewText(R.string.bt_connecting);
+                setProgressBarIndeterminateVisibility(true);
+                break;
+
+            case BluetoothChatService.STATE_CONNECTED:
+                setEmptyViewText(R.string.bt_no_messages);
+                setProgressBarIndeterminateVisibility(false);
+
+                hideInstructionsDialog();
+                sendInitialMessageFromClient();
+                break;
+
+            case BluetoothChatService.STATE_NONE:
+                setEmptyViewText(R.string.bt_no_messages);
+                setProgressBarIndeterminateVisibility(false);
+                break;
+        }
+    }
+
+    private void setEmptyViewText(int textId) {
+        mEmptyReceivedView.setText(textId);
+        mEmptySentView.setText(textId);
+    }
+
+    private void showInstructionsDialog() {
+        if (mInstructionsDialog == null) {
+            mInstructionsDialog = new AlertDialog.Builder(this)
+                    .setIcon(android.R.drawable.ic_dialog_info)
+                    .setTitle(getString(R.string.bt_waiting))
+                    .setMessage(getString(mSecure
+                            ? R.string.bt_secure_server_instructions
+                            : R.string.bt_insecure_server_instructions))
+                    .setPositiveButton(android.R.string.ok, null)
+                    .create();
+        }
+        mInstructionsDialog.show();
+    }
+
+    private void hideInstructionsDialog() {
+        if (mInstructionsDialog != null) {
+            mInstructionsDialog.hide();
+        }
+    }
+
+    private void sendInitialMessageFromClient() {
+        if (!mServer) {
+            sendMessage(0);
+        }
+    }
+
+    private void sendMessage(int number) {
+        String message = "Message " + number + " to "
+                + (mRemoteDeviceName != null ? mRemoteDeviceName : "")
+                + MESSAGE_DELIMITER;
+        mChatService.write(message.getBytes());
+    }
+
+    private void handleMessageRead(Message msg) {
+        String chunk = new String((byte[]) msg.obj, 0, msg.arg1);
+        mMessageBuffer.append(chunk);
+
+        int delimiterIndex = mMessageBuffer.indexOf(MESSAGE_DELIMITER);
+        if (delimiterIndex != -1) {
+            String message = mMessageBuffer.substring(0, delimiterIndex); // Chop off delimiter
+            mMessageBuffer.delete(0, delimiterIndex + 1);
+            addNewMessage(message);
+        }
+    }
+
+    private void addNewMessage(String msg) {
+        mReceivedMessagesAdapter.add(msg);
+        Matcher matcher = MESSAGE_PATTERN.matcher(msg);
+        if (matcher.matches()) {
+            int number = Integer.valueOf(matcher.group(1));
+            if (mServer && number == 10 || !mServer && number == 11) {
+                getPassButton().setEnabled(true);
+            }
+            if (number <= 10) {
+                sendMessage(number + 1);
+            }
+        }
+    }
+
+    private void handleMessageWrite(Message msg) {
+        String sentMessage = new String((byte[]) msg.obj).trim(); // Chop off delimiter
+        mSentMessagesAdapter.add(sentMessage);
+    }
+
+    private void handleDeviceName(Message msg) {
+        mRemoteDeviceName = msg.getData().getString(BluetoothChatService.DEVICE_NAME);
+    }
+
+    private void handleToast(Message msg) {
+        String toast = msg.getData().getString(BluetoothChatService.TOAST);
+        Toast.makeText(this, toast, Toast.LENGTH_LONG).show();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mChatService.stop();
+    }
+}