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