diff options
author | Brett Chabot <brettchabot@android.com> | 2011-05-27 17:09:06 -0700 |
---|---|---|
committer | Brett Chabot <brettchabot@android.com> | 2011-06-01 16:04:30 -0700 |
commit | b525f089cf0f580ccb26e80bbb0db1376ba554cc (patch) | |
tree | 7751d22496b77f9e59b5ed09be939de3c85db4dc /core/tests/bluetoothtests | |
parent | 73e0bc805a143d8cc2202fccb73230459edc6869 (diff) | |
download | frameworks_base-b525f089cf0f580ccb26e80bbb0db1376ba554cc.zip frameworks_base-b525f089cf0f580ccb26e80bbb0db1376ba554cc.tar.gz frameworks_base-b525f089cf0f580ccb26e80bbb0db1376ba554cc.tar.bz2 |
Move bluetooth tests to their own package.
Bug 4501764
Change-Id: Id65de5d96516720e75dfbb5d522f465187e73913
Diffstat (limited to 'core/tests/bluetoothtests')
6 files changed, 2234 insertions, 0 deletions
diff --git a/core/tests/bluetoothtests/Android.mk b/core/tests/bluetoothtests/Android.mk new file mode 100644 index 0000000..4a1d18c --- /dev/null +++ b/core/tests/bluetoothtests/Android.mk @@ -0,0 +1,17 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests + +# Include all test java files. +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) + +LOCAL_JAVA_LIBRARIES := android.test.runner +LOCAL_PACKAGE_NAME := BluetoothTests +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/core/tests/bluetoothtests/AndroidManifest.xml b/core/tests/bluetoothtests/AndroidManifest.xml new file mode 100644 index 0000000..96db035 --- /dev/null +++ b/core/tests/bluetoothtests/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.bluetooth.tests" > + + <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + + <application > + <uses-library android:name="android.test.runner" /> + </application> + <instrumentation android:name="android.bluetooth.BluetoothTestRunner" + android:targetPackage="com.android.bluetooth.tests" + android:label="Bluetooth Tests" /> + +</manifest> diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothRebootStressTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothRebootStressTest.java new file mode 100644 index 0000000..33e9dd7 --- /dev/null +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothRebootStressTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.content.Context; +import android.test.InstrumentationTestCase; + +/** + * Instrumentation test case for stress test involving rebooting the device. + * <p> + * This test case tests that bluetooth is enabled after a device reboot. Because + * the device will reboot, the instrumentation must be driven by a script on the + * host side. + */ +public class BluetoothRebootStressTest extends InstrumentationTestCase { + private static final String TAG = "BluetoothRebootStressTest"; + private static final String OUTPUT_FILE = "BluetoothRebootStressTestOutput.txt"; + + private BluetoothTestUtils mTestUtils; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + Context context = getInstrumentation().getTargetContext(); + mTestUtils = new BluetoothTestUtils(context, TAG, OUTPUT_FILE); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + + mTestUtils.close(); + } + + /** + * Test method used to start the test by turning bluetooth on. + */ + public void testStart() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + mTestUtils.enable(adapter); + } + + /** + * Test method used in the middle iterations of the test to check if + * bluetooth is on. Does not toggle bluetooth after the check. Assumes that + * bluetooth has been turned on by {@code #testStart()} + */ + public void testMiddleNoToggle() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + + assertTrue(adapter.isEnabled()); + } + + /** + * Test method used in the middle iterations of the test to check if + * bluetooth is on. Toggles bluetooth after the check. Assumes that + * bluetooth has been turned on by {@code #testStart()} + */ + public void testMiddleToggle() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + + assertTrue(adapter.isEnabled()); + + mTestUtils.disable(adapter); + mTestUtils.enable(adapter); + } + + /** + * Test method used in the stop the test by turning bluetooth off. Assumes + * that bluetooth has been turned on by {@code #testStart()} + */ + public void testStop() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + + assertTrue(adapter.isEnabled()); + + mTestUtils.disable(adapter); + } +} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java new file mode 100644 index 0000000..abd7d9a --- /dev/null +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.content.Context; +import android.test.InstrumentationTestCase; + +/** + * Stress test suite for Bluetooth related functions. + * + * Includes tests for enabling/disabling bluetooth, enabling/disabling discoverable mode, + * starting/stopping scans, connecting/disconnecting to HFP, A2DP, HID, PAN profiles, and verifying + * that remote connections/disconnections occur for the PAN profile. + * <p> + * This test suite uses {@link android.bluetooth.BluetoothTestRunner} to for parameters such as the + * number of iterations and the addresses of remote Bluetooth devices. + */ +public class BluetoothStressTest extends InstrumentationTestCase { + private static final String TAG = "BluetoothStressTest"; + private static final String OUTPUT_FILE = "BluetoothStressTestOutput.txt"; + + private BluetoothTestUtils mTestUtils; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + Context context = getInstrumentation().getTargetContext(); + mTestUtils = new BluetoothTestUtils(context, TAG, OUTPUT_FILE); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + + mTestUtils.close(); + } + + /** + * Stress test for enabling and disabling Bluetooth. + */ + public void testEnable() { + int iterations = BluetoothTestRunner.sEnableIterations; + if (iterations == 0) { + return; + } + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + mTestUtils.disable(adapter); + + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("enable iteration " + (i + 1) + " of " + iterations); + mTestUtils.enable(adapter); + mTestUtils.disable(adapter); + } + } + + /** + * Stress test for putting the device in and taking the device out of discoverable mode. + */ + public void testDiscoverable() { + int iterations = BluetoothTestRunner.sDiscoverableIterations; + if (iterations == 0) { + return; + } + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + mTestUtils.disable(adapter); + mTestUtils.enable(adapter); + mTestUtils.undiscoverable(adapter); + + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("discoverable iteration " + (i + 1) + " of " + iterations); + mTestUtils.discoverable(adapter); + mTestUtils.undiscoverable(adapter); + } + + mTestUtils.disable(adapter); + } + + /** + * Stress test for starting and stopping Bluetooth scans. + */ + public void testScan() { + int iterations = BluetoothTestRunner.sScanIterations; + if (iterations == 0) { + return; + } + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + mTestUtils.disable(adapter); + mTestUtils.enable(adapter); + mTestUtils.stopScan(adapter); + + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("scan iteration " + (i + 1) + " of " + iterations); + mTestUtils.startScan(adapter); + mTestUtils.stopScan(adapter); + } + + mTestUtils.disable(adapter); + } + + /** + * Stress test for enabling and disabling the PAN NAP profile. + */ + public void testEnablePan() { + int iterations = BluetoothTestRunner.sEnablePanIterations; + if (iterations == 0) { + return; + } + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + mTestUtils.disable(adapter); + mTestUtils.enable(adapter); + mTestUtils.disablePan(adapter); + + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("testEnablePan iteration " + (i + 1) + " of " + + iterations); + mTestUtils.enablePan(adapter); + mTestUtils.disablePan(adapter); + } + + mTestUtils.disable(adapter); + } + + /** + * Stress test for pairing and unpairing with a remote device. + * <p> + * In this test, the local device initiates pairing with a remote device, and then unpairs with + * the device after the pairing has successfully completed. + */ + public void testPair() { + int iterations = BluetoothTestRunner.sPairIterations; + if (iterations == 0) { + return; + } + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); + mTestUtils.enable(adapter); + mTestUtils.unpair(adapter, device); + + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("pair iteration " + (i + 1) + " of " + iterations); + mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); + mTestUtils.unpair(adapter, device); + } + mTestUtils.disable(adapter); + } + + /** + * Stress test for accepting a pairing request and unpairing with a remote device. + * <p> + * In this test, the local device waits for a pairing request from a remote device. It accepts + * the request and then unpairs after the paring has successfully completed. + */ + public void testAcceptPair() { + int iterations = BluetoothTestRunner.sPairIterations; + if (iterations == 0) { + return; + } + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); + mTestUtils.enable(adapter); + mTestUtils.unpair(adapter, device); + + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("acceptPair iteration " + (i + 1) + " of " + iterations); + mTestUtils.acceptPair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); + mTestUtils.unpair(adapter, device); + } + mTestUtils.disable(adapter); + } + + /** + * Stress test for connecting and disconnecting with an A2DP source. + * <p> + * In this test, the local device plays the role of an A2DP sink, and initiates connections and + * disconnections with an A2DP source. + */ + public void testConnectA2dp() { + int iterations = BluetoothTestRunner.sConnectA2dpIterations; + if (iterations == 0) { + return; + } + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); + mTestUtils.enable(adapter); + mTestUtils.unpair(adapter, device); + mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.A2DP, null); + + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("connectA2dp iteration " + (i + 1) + " of " + iterations); + mTestUtils.connectProfile(adapter, device, BluetoothProfile.A2DP, + String.format("connectA2dp(device=%s)", device)); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.A2DP, + String.format("disconnectA2dp(device=%s)", device)); + } + + mTestUtils.unpair(adapter, device); + mTestUtils.disable(adapter); + } + + /** + * Stress test for connecting and disconnecting the HFP with a hands free device. + * <p> + * In this test, the local device plays the role of an HFP audio gateway, and initiates + * connections and disconnections with a hands free device. + */ + public void testConnectHeadset() { + int iterations = BluetoothTestRunner.sConnectHeadsetIterations; + if (iterations == 0) { + return; + } + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); + mTestUtils.enable(adapter); + mTestUtils.unpair(adapter, device); + mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET, null); + + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("connectHeadset iteration " + (i + 1) + " of " + iterations); + mTestUtils.connectProfile(adapter, device, BluetoothProfile.HEADSET, + String.format("connectHeadset(device=%s)", device)); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET, + String.format("disconnectHeadset(device=%s)", device)); + } + + mTestUtils.unpair(adapter, device); + mTestUtils.disable(adapter); + } + + /** + * Stress test for connecting and disconnecting with a HID device. + * <p> + * In this test, the local device plays the role of a HID host, and initiates connections and + * disconnections with a HID device. + */ + public void testConnectInput() { + int iterations = BluetoothTestRunner.sConnectInputIterations; + if (iterations == 0) { + return; + } + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); + mTestUtils.enable(adapter); + mTestUtils.unpair(adapter, device); + mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.INPUT_DEVICE, null); + + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("connectInput iteration " + (i + 1) + " of " + iterations); + mTestUtils.connectProfile(adapter, device, BluetoothProfile.INPUT_DEVICE, + String.format("connectInput(device=%s)", device)); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.INPUT_DEVICE, + String.format("disconnectInput(device=%s)", device)); + } + + mTestUtils.unpair(adapter, device); + mTestUtils.disable(adapter); + } + + /** + * Stress test for connecting and disconnecting with a PAN NAP. + * <p> + * In this test, the local device plays the role of a PANU, and initiates connections and + * disconnections with a NAP. + */ + public void testConnectPan() { + int iterations = BluetoothTestRunner.sConnectPanIterations; + if (iterations == 0) { + return; + } + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); + mTestUtils.enable(adapter); + mTestUtils.unpair(adapter, device); + mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); + + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("connectPan iteration " + (i + 1) + " of " + iterations); + mTestUtils.connectPan(adapter, device); + mTestUtils.disconnectPan(adapter, device); + } + + mTestUtils.unpair(adapter, device); + mTestUtils.disable(adapter); + } + + /** + * Stress test for verifying a PANU connecting and disconnecting with the device. + * <p> + * In this test, the local device plays the role of a NAP which a remote PANU connects and + * disconnects from. + */ + public void testIncomingPanConnection() { + int iterations = BluetoothTestRunner.sConnectPanIterations; + if (iterations == 0) { + return; + } + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); + mTestUtils.enable(adapter); + mTestUtils.disablePan(adapter); + mTestUtils.enablePan(adapter); + mTestUtils.unpair(adapter, device); + mTestUtils.acceptPair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); + + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("incomingPanConnection iteration " + (i + 1) + " of " + + iterations); + mTestUtils.incomingPanConnection(adapter, device); + mTestUtils.incomingPanDisconnection(adapter, device); + } + + mTestUtils.unpair(adapter, device); + mTestUtils.disablePan(adapter); + mTestUtils.disable(adapter); + } + + /** + * Stress test for verifying that AudioManager can open and close SCO connections. + * <p> + * In this test, a HSP connection is opened with an external headset and the SCO connection is + * repeatibly opened and closed. + */ + public void testStartStopSco() { + int iterations = BluetoothTestRunner.sStartStopScoIterations; + if (iterations == 0) { + return; + } + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.disable(adapter); + mTestUtils.enable(adapter); + mTestUtils.unpair(adapter, device); + mTestUtils.pair(adapter, device, BluetoothTestRunner.sDevicePairPasskey, + BluetoothTestRunner.sDevicePairPin); + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET, null); + mTestUtils.connectProfile(adapter, device, BluetoothProfile.HEADSET, null); + mTestUtils.stopSco(adapter, device); + + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("startStopSco iteration " + (i + 1) + " of " + iterations); + mTestUtils.startSco(adapter, device); + mTestUtils.stopSco(adapter, device); + } + + mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET, null); + mTestUtils.unpair(adapter, device); + mTestUtils.disable(adapter); + } +} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java new file mode 100644 index 0000000..64d2c12 --- /dev/null +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import junit.framework.TestSuite; + +import android.os.Bundle; +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; +import android.util.Log; + +/** + * Instrumentation test runner for Bluetooth tests. + * <p> + * To run: + * <pre> + * {@code + * adb shell am instrument \ + * [-e enable_iterations <iterations>] \ + * [-e discoverable_iterations <iterations>] \ + * [-e scan_iterations <iterations>] \ + * [-e enable_pan_iterations <iterations>] \ + * [-e pair_iterations <iterations>] \ + * [-e connect_a2dp_iterations <iterations>] \ + * [-e connect_headset_iterations <iterations>] \ + * [-e connect_input_iterations <iterations>] \ + * [-e connect_pan_iterations <iterations>] \ + * [-e start_stop_sco_iterations <iterations>] \ + * [-e pair_address <address>] \ + * [-e headset_address <address>] \ + * [-e a2dp_address <address>] \ + * [-e input_address <address>] \ + * [-e pan_address <address>] \ + * [-e pair_pin <pin>] \ + * [-e pair_passkey <passkey>] \ + * -w com.android.frameworks.coretests/android.bluetooth.BluetoothTestRunner + * } + * </pre> + */ +public class BluetoothTestRunner extends InstrumentationTestRunner { + private static final String TAG = "BluetoothTestRunner"; + + public static int sEnableIterations = 100; + public static int sDiscoverableIterations = 1000; + public static int sScanIterations = 1000; + public static int sEnablePanIterations = 1000; + public static int sPairIterations = 100; + public static int sConnectHeadsetIterations = 100; + public static int sConnectA2dpIterations = 100; + public static int sConnectInputIterations = 100; + public static int sConnectPanIterations = 100; + public static int sStartStopScoIterations = 100; + + public static String sDeviceAddress = ""; + public static byte[] sDevicePairPin = {'1', '2', '3', '4'}; + public static int sDevicePairPasskey = 123456; + + @Override + public TestSuite getAllTests() { + TestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(BluetoothStressTest.class); + return suite; + } + + @Override + public ClassLoader getLoader() { + return BluetoothTestRunner.class.getClassLoader(); + } + + @Override + public void onCreate(Bundle arguments) { + String val = arguments.getString("enable_iterations"); + if (val != null) { + try { + sEnableIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + val = arguments.getString("discoverable_iterations"); + if (val != null) { + try { + sDiscoverableIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + val = arguments.getString("scan_iterations"); + if (val != null) { + try { + sScanIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + val = arguments.getString("enable_pan_iterations"); + if (val != null) { + try { + sEnablePanIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + val = arguments.getString("pair_iterations"); + if (val != null) { + try { + sPairIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + val = arguments.getString("connect_a2dp_iterations"); + if (val != null) { + try { + sConnectA2dpIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + val = arguments.getString("connect_headset_iterations"); + if (val != null) { + try { + sConnectHeadsetIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + val = arguments.getString("connect_input_iterations"); + if (val != null) { + try { + sConnectInputIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + val = arguments.getString("connect_pan_iterations"); + if (val != null) { + try { + sConnectPanIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + val = arguments.getString("start_stop_sco_iterations"); + if (val != null) { + try { + sStartStopScoIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + val = arguments.getString("device_address"); + if (val != null) { + sDeviceAddress = val; + } + + val = arguments.getString("device_pair_pin"); + if (val != null) { + byte[] pin = BluetoothDevice.convertPinToBytes(val); + if (pin != null) { + sDevicePairPin = pin; + } + } + + val = arguments.getString("device_pair_passkey"); + if (val != null) { + try { + sDevicePairPasskey = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + Log.i(TAG, String.format("enable_iterations=%d", sEnableIterations)); + Log.i(TAG, String.format("discoverable_iterations=%d", sDiscoverableIterations)); + Log.i(TAG, String.format("scan_iterations=%d", sScanIterations)); + Log.i(TAG, String.format("pair_iterations=%d", sPairIterations)); + Log.i(TAG, String.format("connect_a2dp_iterations=%d", sConnectA2dpIterations)); + Log.i(TAG, String.format("connect_headset_iterations=%d", sConnectHeadsetIterations)); + Log.i(TAG, String.format("connect_input_iterations=%d", sConnectInputIterations)); + Log.i(TAG, String.format("connect_pan_iterations=%d", sConnectPanIterations)); + Log.i(TAG, String.format("start_stop_sco_iterations=%d", sStartStopScoIterations)); + Log.i(TAG, String.format("device_address=%s", sDeviceAddress)); + Log.i(TAG, String.format("device_pair_pin=%s", new String(sDevicePairPin))); + Log.i(TAG, String.format("device_pair_passkey=%d", sDevicePairPasskey)); + + // Call onCreate last since we want to set the static variables first. + super.onCreate(arguments); + } +} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java new file mode 100644 index 0000000..f1dd8fe --- /dev/null +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java @@ -0,0 +1,1487 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothProfile; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.os.Environment; +import android.util.Log; + +import junit.framework.Assert; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class BluetoothTestUtils extends Assert { + + /** Timeout for enable/disable in ms. */ + private static final int ENABLE_DISABLE_TIMEOUT = 20000; + /** Timeout for discoverable/undiscoverable in ms. */ + private static final int DISCOVERABLE_UNDISCOVERABLE_TIMEOUT = 5000; + /** Timeout for starting/stopping a scan in ms. */ + private static final int START_STOP_SCAN_TIMEOUT = 5000; + /** Timeout for pair/unpair in ms. */ + private static final int PAIR_UNPAIR_TIMEOUT = 20000; + /** Timeout for connecting/disconnecting a profile in ms. */ + private static final int CONNECT_DISCONNECT_PROFILE_TIMEOUT = 20000; + /** Timeout to start or stop a SCO channel in ms. */ + private static final int START_STOP_SCO_TIMEOUT = 10000; + /** Timeout to connect a profile proxy in ms. */ + private static final int CONNECT_PROXY_TIMEOUT = 5000; + /** Time between polls in ms. */ + private static final int POLL_TIME = 100; + + private abstract class FlagReceiver extends BroadcastReceiver { + private int mExpectedFlags = 0; + private int mFiredFlags = 0; + private long mCompletedTime = -1; + + public FlagReceiver(int expectedFlags) { + mExpectedFlags = expectedFlags; + } + + public int getFiredFlags() { + synchronized (this) { + return mFiredFlags; + } + } + + public long getCompletedTime() { + synchronized (this) { + return mCompletedTime; + } + } + + protected void setFiredFlag(int flag) { + synchronized (this) { + mFiredFlags |= flag; + if ((mFiredFlags & mExpectedFlags) == mExpectedFlags) { + mCompletedTime = System.currentTimeMillis(); + } + } + } + } + + private class BluetoothReceiver extends FlagReceiver { + private static final int DISCOVERY_STARTED_FLAG = 1; + private static final int DISCOVERY_FINISHED_FLAG = 1 << 1; + private static final int SCAN_MODE_NONE_FLAG = 1 << 2; + private static final int SCAN_MODE_CONNECTABLE_FLAG = 1 << 3; + private static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG = 1 << 4; + private static final int STATE_OFF_FLAG = 1 << 5; + private static final int STATE_TURNING_ON_FLAG = 1 << 6; + private static final int STATE_ON_FLAG = 1 << 7; + private static final int STATE_TURNING_OFF_FLAG = 1 << 8; + + public BluetoothReceiver(int expectedFlags) { + super(expectedFlags); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) { + setFiredFlag(DISCOVERY_STARTED_FLAG); + } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) { + setFiredFlag(DISCOVERY_FINISHED_FLAG); + } else if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) { + int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, -1); + assertNotSame(-1, mode); + switch (mode) { + case BluetoothAdapter.SCAN_MODE_NONE: + setFiredFlag(SCAN_MODE_NONE_FLAG); + break; + case BluetoothAdapter.SCAN_MODE_CONNECTABLE: + setFiredFlag(SCAN_MODE_CONNECTABLE_FLAG); + break; + case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: + setFiredFlag(SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG); + break; + } + } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); + assertNotSame(-1, state); + switch (state) { + case BluetoothAdapter.STATE_OFF: + setFiredFlag(STATE_OFF_FLAG); + break; + case BluetoothAdapter.STATE_TURNING_ON: + setFiredFlag(STATE_TURNING_ON_FLAG); + break; + case BluetoothAdapter.STATE_ON: + setFiredFlag(STATE_ON_FLAG); + break; + case BluetoothAdapter.STATE_TURNING_OFF: + setFiredFlag(STATE_TURNING_OFF_FLAG); + break; + } + } + } + } + + private class PairReceiver extends FlagReceiver { + private static final int STATE_BONDED_FLAG = 1; + private static final int STATE_BONDING_FLAG = 1 << 1; + private static final int STATE_NONE_FLAG = 1 << 2; + + private BluetoothDevice mDevice; + private int mPasskey; + private byte[] mPin; + + public PairReceiver(BluetoothDevice device, int passkey, byte[] pin, int expectedFlags) { + super(expectedFlags); + + mDevice = device; + mPasskey = passkey; + mPin = pin; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (!mDevice.equals(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))) { + return; + } + + if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) { + int varient = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, -1); + assertNotSame(-1, varient); + switch (varient) { + case BluetoothDevice.PAIRING_VARIANT_PIN: + mDevice.setPin(mPin); + break; + case BluetoothDevice.PAIRING_VARIANT_PASSKEY: + mDevice.setPasskey(mPasskey); + break; + case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: + case BluetoothDevice.PAIRING_VARIANT_CONSENT: + mDevice.setPairingConfirmation(true); + break; + case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: + mDevice.setRemoteOutOfBandData(); + break; + } + } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { + int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); + assertNotSame(-1, state); + switch (state) { + case BluetoothDevice.BOND_NONE: + setFiredFlag(STATE_NONE_FLAG); + break; + case BluetoothDevice.BOND_BONDING: + setFiredFlag(STATE_BONDING_FLAG); + break; + case BluetoothDevice.BOND_BONDED: + setFiredFlag(STATE_BONDED_FLAG); + break; + } + } + } + } + + private class ConnectProfileReceiver extends FlagReceiver { + private static final int STATE_DISCONNECTED_FLAG = 1; + private static final int STATE_CONNECTING_FLAG = 1 << 1; + private static final int STATE_CONNECTED_FLAG = 1 << 2; + private static final int STATE_DISCONNECTING_FLAG = 1 << 3; + + private BluetoothDevice mDevice; + private int mProfile; + private String mConnectionAction; + + public ConnectProfileReceiver(BluetoothDevice device, int profile, int expectedFlags) { + super(expectedFlags); + + mDevice = device; + mProfile = profile; + + switch (mProfile) { + case BluetoothProfile.A2DP: + mConnectionAction = BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED; + break; + case BluetoothProfile.HEADSET: + mConnectionAction = BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED; + break; + case BluetoothProfile.INPUT_DEVICE: + mConnectionAction = BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED; + break; + case BluetoothProfile.PAN: + mConnectionAction = BluetoothPan.ACTION_CONNECTION_STATE_CHANGED; + break; + default: + mConnectionAction = null; + } + } + + @Override + public void onReceive(Context context, Intent intent) { + if (mConnectionAction != null && mConnectionAction.equals(intent.getAction())) { + if (!mDevice.equals(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))) { + return; + } + + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); + assertNotSame(-1, state); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + setFiredFlag(STATE_DISCONNECTED_FLAG); + break; + case BluetoothProfile.STATE_CONNECTING: + setFiredFlag(STATE_CONNECTING_FLAG); + break; + case BluetoothProfile.STATE_CONNECTED: + setFiredFlag(STATE_CONNECTED_FLAG); + break; + case BluetoothProfile.STATE_DISCONNECTING: + setFiredFlag(STATE_DISCONNECTING_FLAG); + break; + } + } + } + } + + private class ConnectPanReceiver extends ConnectProfileReceiver { + private int mRole; + + public ConnectPanReceiver(BluetoothDevice device, int role, int expectedFlags) { + super(device, BluetoothProfile.PAN, expectedFlags); + + mRole = role; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (mRole != intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, -1)) { + return; + } + + super.onReceive(context, intent); + } + } + + private class StartStopScoReceiver extends FlagReceiver { + private static final int STATE_CONNECTED_FLAG = 1; + private static final int STATE_DISCONNECTED_FLAG = 1 << 1; + + public StartStopScoReceiver(int expectedFlags) { + super(expectedFlags); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED.equals(intent.getAction())) { + int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, + AudioManager.SCO_AUDIO_STATE_ERROR); + assertNotSame(AudioManager.SCO_AUDIO_STATE_ERROR, state); + switch(state) { + case AudioManager.SCO_AUDIO_STATE_CONNECTED: + setFiredFlag(STATE_CONNECTED_FLAG); + break; + case AudioManager.SCO_AUDIO_STATE_DISCONNECTED: + setFiredFlag(STATE_DISCONNECTED_FLAG); + break; + } + } + } + } + + private BluetoothProfile.ServiceListener mServiceListener = + new BluetoothProfile.ServiceListener() { + @Override + public void onServiceConnected(int profile, BluetoothProfile proxy) { + synchronized (this) { + switch (profile) { + case BluetoothProfile.A2DP: + mA2dp = (BluetoothA2dp) proxy; + break; + case BluetoothProfile.HEADSET: + mHeadset = (BluetoothHeadset) proxy; + break; + case BluetoothProfile.INPUT_DEVICE: + mInput = (BluetoothInputDevice) proxy; + break; + case BluetoothProfile.PAN: + mPan = (BluetoothPan) proxy; + break; + } + } + } + + @Override + public void onServiceDisconnected(int profile) { + synchronized (this) { + switch (profile) { + case BluetoothProfile.A2DP: + mA2dp = null; + break; + case BluetoothProfile.HEADSET: + mHeadset = null; + break; + case BluetoothProfile.INPUT_DEVICE: + mInput = null; + break; + case BluetoothProfile.PAN: + mPan = null; + break; + } + } + } + }; + + private List<BroadcastReceiver> mReceivers = new ArrayList<BroadcastReceiver>(); + + private BufferedWriter mOutputWriter; + private String mTag; + private String mOutputFile; + + private Context mContext; + private BluetoothA2dp mA2dp = null; + private BluetoothHeadset mHeadset = null; + private BluetoothInputDevice mInput = null; + private BluetoothPan mPan = null; + + /** + * Creates a utility instance for testing Bluetooth. + * + * @param context The context of the application using the utility. + * @param tag The log tag of the application using the utility. + */ + public BluetoothTestUtils(Context context, String tag) { + this(context, tag, null); + } + + /** + * Creates a utility instance for testing Bluetooth. + * + * @param context The context of the application using the utility. + * @param tag The log tag of the application using the utility. + * @param outputFile The path to an output file if the utility is to write results to a + * separate file. + */ + public BluetoothTestUtils(Context context, String tag, String outputFile) { + mContext = context; + mTag = tag; + mOutputFile = outputFile; + + if (mOutputFile == null) { + mOutputWriter = null; + } else { + try { + mOutputWriter = new BufferedWriter(new FileWriter(new File( + Environment.getExternalStorageDirectory(), mOutputFile), true)); + } catch (IOException e) { + Log.w(mTag, "Test output file could not be opened", e); + mOutputWriter = null; + } + } + } + + /** + * Closes the utility instance and unregisters any BroadcastReceivers. + */ + public void close() { + while (!mReceivers.isEmpty()) { + mContext.unregisterReceiver(mReceivers.remove(0)); + } + + if (mOutputWriter != null) { + try { + mOutputWriter.close(); + } catch (IOException e) { + Log.w(mTag, "Test output file could not be closed", e); + } + } + } + + /** + * Enables Bluetooth and checks to make sure that Bluetooth was turned on and that the correct + * actions were broadcast. + * + * @param adapter The BT adapter. + */ + public void enable(BluetoothAdapter adapter) { + int mask = (BluetoothReceiver.STATE_TURNING_ON_FLAG | BluetoothReceiver.STATE_ON_FLAG + | BluetoothReceiver.SCAN_MODE_CONNECTABLE_FLAG); + long start = -1; + BluetoothReceiver receiver = getBluetoothReceiver(mask); + + int state = adapter.getState(); + switch (state) { + case BluetoothAdapter.STATE_ON: + assertTrue(adapter.isEnabled()); + removeReceiver(receiver); + return; + case BluetoothAdapter.STATE_TURNING_ON: + assertFalse(adapter.isEnabled()); + mask = 0; // Don't check for received intents since we might have missed them. + break; + case BluetoothAdapter.STATE_OFF: + assertFalse(adapter.isEnabled()); + start = System.currentTimeMillis(); + assertTrue(adapter.enable()); + break; + case BluetoothAdapter.STATE_TURNING_OFF: + start = System.currentTimeMillis(); + assertTrue(adapter.enable()); + break; + default: + removeReceiver(receiver); + fail(String.format("enable() invalid state: state=%d", state)); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < ENABLE_DISABLE_TIMEOUT) { + state = adapter.getState(); + if (state == BluetoothAdapter.STATE_ON + && (receiver.getFiredFlags() & mask) == mask) { + assertTrue(adapter.isEnabled()); + long finish = receiver.getCompletedTime(); + if (start != -1 && finish != -1) { + writeOutput(String.format("enable() completed in %d ms", (finish - start))); + } else { + writeOutput("enable() completed"); + } + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("enable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", + state, BluetoothAdapter.STATE_ON, firedFlags, mask)); + } + + /** + * Disables Bluetooth and checks to make sure that Bluetooth was turned off and that the correct + * actions were broadcast. + * + * @param adapter The BT adapter. + */ + public void disable(BluetoothAdapter adapter) { + int mask = (BluetoothReceiver.STATE_TURNING_OFF_FLAG | BluetoothReceiver.STATE_OFF_FLAG + | BluetoothReceiver.SCAN_MODE_NONE_FLAG); + long start = -1; + BluetoothReceiver receiver = getBluetoothReceiver(mask); + + int state = adapter.getState(); + switch (state) { + case BluetoothAdapter.STATE_OFF: + assertFalse(adapter.isEnabled()); + removeReceiver(receiver); + return; + case BluetoothAdapter.STATE_TURNING_ON: + assertFalse(adapter.isEnabled()); + start = System.currentTimeMillis(); + break; + case BluetoothAdapter.STATE_ON: + assertTrue(adapter.isEnabled()); + start = System.currentTimeMillis(); + assertTrue(adapter.disable()); + break; + case BluetoothAdapter.STATE_TURNING_OFF: + assertFalse(adapter.isEnabled()); + mask = 0; // Don't check for received intents since we might have missed them. + break; + default: + removeReceiver(receiver); + fail(String.format("disable() invalid state: state=%d", state)); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < ENABLE_DISABLE_TIMEOUT) { + state = adapter.getState(); + if (state == BluetoothAdapter.STATE_OFF + && (receiver.getFiredFlags() & mask) == mask) { + assertFalse(adapter.isEnabled()); + long finish = receiver.getCompletedTime(); + if (start != -1 && finish != -1) { + writeOutput(String.format("disable() completed in %d ms", (finish - start))); + } else { + writeOutput("disable() completed"); + } + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("disable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", + state, BluetoothAdapter.STATE_OFF, firedFlags, mask)); + } + + /** + * Puts the local device into discoverable mode and checks to make sure that the local device + * is in discoverable mode and that the correct actions were broadcast. + * + * @param adapter The BT adapter. + */ + public void discoverable(BluetoothAdapter adapter) { + int mask = BluetoothReceiver.SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG; + + if (!adapter.isEnabled()) { + fail("discoverable() bluetooth not enabled"); + } + + int scanMode = adapter.getScanMode(); + if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { + return; + } + + BluetoothReceiver receiver = getBluetoothReceiver(mask); + + assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE, scanMode); + long start = System.currentTimeMillis(); + assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)); + + while (System.currentTimeMillis() - start < DISCOVERABLE_UNDISCOVERABLE_TIMEOUT) { + scanMode = adapter.getScanMode(); + if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE + && (receiver.getFiredFlags() & mask) == mask) { + writeOutput(String.format("discoverable() completed in %d ms", + (receiver.getCompletedTime() - start))); + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("discoverable() timeout: scanMode=%d (expected %d), flags=0x%x " + + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, + firedFlags, mask)); + } + + /** + * Puts the local device into connectable only mode and checks to make sure that the local + * device is in in connectable mode and that the correct actions were broadcast. + * + * @param adapter The BT adapter. + */ + public void undiscoverable(BluetoothAdapter adapter) { + int mask = BluetoothReceiver.SCAN_MODE_CONNECTABLE_FLAG; + + if (!adapter.isEnabled()) { + fail("undiscoverable() bluetooth not enabled"); + } + + int scanMode = adapter.getScanMode(); + if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) { + return; + } + + BluetoothReceiver receiver = getBluetoothReceiver(mask); + + assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, scanMode); + long start = System.currentTimeMillis(); + assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE)); + + while (System.currentTimeMillis() - start < DISCOVERABLE_UNDISCOVERABLE_TIMEOUT) { + scanMode = adapter.getScanMode(); + if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE + && (receiver.getFiredFlags() & mask) == mask) { + writeOutput(String.format("undiscoverable() completed in %d ms", + (receiver.getCompletedTime() - start))); + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("undiscoverable() timeout: scanMode=%d (expected %d), flags=0x%x " + + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE, firedFlags, + mask)); + } + + /** + * Starts a scan for remote devices and checks to make sure that the local device is scanning + * and that the correct actions were broadcast. + * + * @param adapter The BT adapter. + */ + public void startScan(BluetoothAdapter adapter) { + int mask = BluetoothReceiver.DISCOVERY_STARTED_FLAG; + + if (!adapter.isEnabled()) { + fail("startScan() bluetooth not enabled"); + } + + if (adapter.isDiscovering()) { + return; + } + + BluetoothReceiver receiver = getBluetoothReceiver(mask); + + long start = System.currentTimeMillis(); + assertTrue(adapter.startDiscovery()); + + while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) { + if (adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) { + writeOutput(String.format("startScan() completed in %d ms", + (receiver.getCompletedTime() - start))); + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("startScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)", + adapter.isDiscovering(), firedFlags, mask)); + } + + /** + * Stops a scan for remote devices and checks to make sure that the local device is not scanning + * and that the correct actions were broadcast. + * + * @param adapter The BT adapter. + */ + public void stopScan(BluetoothAdapter adapter) { + int mask = BluetoothReceiver.DISCOVERY_FINISHED_FLAG; + + if (!adapter.isEnabled()) { + fail("stopScan() bluetooth not enabled"); + } + + if (!adapter.isDiscovering()) { + return; + } + + BluetoothReceiver receiver = getBluetoothReceiver(mask); + + long start = System.currentTimeMillis(); + // TODO: put assertTrue() around cancelDiscovery() once it starts returning true. + adapter.cancelDiscovery(); + + while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) { + if (!adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) { + writeOutput(String.format("stopScan() completed in %d ms", + (receiver.getCompletedTime() - start))); + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("stopScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)", + adapter.isDiscovering(), firedFlags, mask)); + + } + + /** + * Enables PAN tethering on the local device and checks to make sure that tethering is enabled. + * + * @param adapter The BT adapter. + */ + public void enablePan(BluetoothAdapter adapter) { + if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); + assertNotNull(mPan); + + long start = System.currentTimeMillis(); + mPan.setBluetoothTethering(true); + long stop = System.currentTimeMillis(); + assertTrue(mPan.isTetheringOn()); + + writeOutput(String.format("enablePan() completed in %d ms", (stop - start))); + } + + /** + * Disables PAN tethering on the local device and checks to make sure that tethering is + * disabled. + * + * @param adapter The BT adapter. + */ + public void disablePan(BluetoothAdapter adapter) { + if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); + assertNotNull(mPan); + + long start = System.currentTimeMillis(); + mPan.setBluetoothTethering(false); + long stop = System.currentTimeMillis(); + assertFalse(mPan.isTetheringOn()); + + writeOutput(String.format("disablePan() completed in %d ms", (stop - start))); + } + + /** + * Initiates a pairing with a remote device and checks to make sure that the devices are paired + * and that the correct actions were broadcast. + * + * @param adapter The BT adapter. + * @param device The remote device. + * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. + * @param pin The pairing pin if pairing requires a pin. Any value if not. + */ + public void pair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, byte[] pin) { + pairOrAcceptPair(adapter, device, passkey, pin, true); + } + + /** + * Accepts a pairing with a remote device and checks to make sure that the devices are paired + * and that the correct actions were broadcast. + * + * @param adapter The BT adapter. + * @param device The remote device. + * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. + * @param pin The pairing pin if pairing requires a pin. Any value if not. + */ + public void acceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, + byte[] pin) { + pairOrAcceptPair(adapter, device, passkey, pin, false); + } + + /** + * Helper method used by {@link #pair(BluetoothAdapter, BluetoothDevice, int, byte[])} and + * {@link #acceptPair(BluetoothAdapter, BluetoothDevice, int, byte[])} to either pair or accept + * a pairing request. + * + * @param adapter The BT adapter. + * @param device The remote device. + * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. + * @param pin The pairing pin if pairing requires a pin. Any value if not. + * @param shouldPair Whether to pair or accept the pair. + */ + private void pairOrAcceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, + byte[] pin, boolean shouldPair) { + int mask = PairReceiver.STATE_BONDING_FLAG | PairReceiver.STATE_BONDED_FLAG; + long start = -1; + String methodName; + if (shouldPair) { + methodName = String.format("pair(device=%s)", device); + } else { + methodName = String.format("acceptPair(device=%s)", device); + } + + if (!adapter.isEnabled()) { + fail(String.format("%s bluetooth not enabled", methodName)); + } + + PairReceiver receiver = getPairReceiver(device, passkey, pin, mask); + + int state = device.getBondState(); + switch (state) { + case BluetoothDevice.BOND_NONE: + assertFalse(adapter.getBondedDevices().contains(device)); + start = System.currentTimeMillis(); + if (shouldPair) { + assertTrue(device.createBond()); + } + break; + case BluetoothDevice.BOND_BONDING: + mask = 0; // Don't check for received intents since we might have missed them. + break; + case BluetoothDevice.BOND_BONDED: + assertTrue(adapter.getBondedDevices().contains(device)); + return; + default: + removeReceiver(receiver); + fail(String.format("%s invalid state: state=%d", methodName, state)); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) { + state = device.getBondState(); + if (state == BluetoothDevice.BOND_BONDED && (receiver.getFiredFlags() & mask) == mask) { + assertTrue(adapter.getBondedDevices().contains(device)); + long finish = receiver.getCompletedTime(); + if (start != -1 && finish != -1) { + writeOutput(String.format("%s completed in %d ms", methodName, + (finish - start))); + } else { + writeOutput(String.format("%s completed", methodName)); + } + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", + methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask)); + } + + /** + * Deletes a pairing with a remote device and checks to make sure that the devices are unpaired + * and that the correct actions were broadcast. + * + * @param adapter The BT adapter. + * @param device The remote device. + */ + public void unpair(BluetoothAdapter adapter, BluetoothDevice device) { + int mask = PairReceiver.STATE_NONE_FLAG; + long start = -1; + String methodName = String.format("unpair(device=%s)", device); + + if (!adapter.isEnabled()) { + fail(String.format("%s bluetooth not enabled", methodName)); + } + + PairReceiver receiver = getPairReceiver(device, 0, null, mask); + + int state = device.getBondState(); + switch (state) { + case BluetoothDevice.BOND_NONE: + assertFalse(adapter.getBondedDevices().contains(device)); + removeReceiver(receiver); + return; + case BluetoothDevice.BOND_BONDING: + start = System.currentTimeMillis(); + assertTrue(device.removeBond()); + break; + case BluetoothDevice.BOND_BONDED: + assertTrue(adapter.getBondedDevices().contains(device)); + start = System.currentTimeMillis(); + assertTrue(device.removeBond()); + break; + default: + removeReceiver(receiver); + fail(String.format("%s invalid state: state=%d", methodName, state)); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) { + if (device.getBondState() == BluetoothDevice.BOND_NONE + && (receiver.getFiredFlags() & mask) == mask) { + assertFalse(adapter.getBondedDevices().contains(device)); + long finish = receiver.getCompletedTime(); + if (start != -1 && finish != -1) { + writeOutput(String.format("%s completed in %d ms", methodName, + (finish - start))); + } else { + writeOutput(String.format("%s completed", methodName)); + } + removeReceiver(receiver); + return; + } + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", + methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask)); + } + + /** + * Connects a profile from the local device to a remote device and checks to make sure that the + * profile is connected and that the correct actions were broadcast. + * + * @param adapter The BT adapter. + * @param device The remote device. + * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP}, + * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}. + * @param methodName The method name to printed in the logs. If null, will be + * "connectProfile(profile=<profile>, device=<device>)" + */ + public void connectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile, + String methodName) { + if (methodName == null) { + methodName = String.format("connectProfile(profile=%d, device=%s)", profile, device); + } + int mask = (ConnectProfileReceiver.STATE_CONNECTING_FLAG + | ConnectProfileReceiver.STATE_CONNECTED_FLAG); + long start = -1; + + if (!adapter.isEnabled()) { + fail(String.format("%s bluetooth not enabled", methodName)); + } + + if (!adapter.getBondedDevices().contains(device)) { + fail(String.format("%s device not paired", methodName)); + } + + BluetoothProfile proxy = connectProxy(adapter, profile); + assertNotNull(proxy); + + ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask); + + int state = proxy.getConnectionState(device); + switch (state) { + case BluetoothProfile.STATE_CONNECTED: + removeReceiver(receiver); + return; + case BluetoothProfile.STATE_CONNECTING: + mask = 0; // Don't check for received intents since we might have missed them. + break; + case BluetoothProfile.STATE_DISCONNECTED: + case BluetoothProfile.STATE_DISCONNECTING: + start = System.currentTimeMillis(); + assertTrue(proxy.connect(device)); + break; + default: + removeReceiver(receiver); + fail(String.format("%s invalid state: state=%d", methodName, state)); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { + state = proxy.getConnectionState(device); + if (state == BluetoothProfile.STATE_CONNECTED + && (receiver.getFiredFlags() & mask) == mask) { + long finish = receiver.getCompletedTime(); + if (start != -1 && finish != -1) { + writeOutput(String.format("%s completed in %d ms", methodName, + (finish - start))); + } else { + writeOutput(String.format("%s completed", methodName)); + } + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", + methodName, state, BluetoothProfile.STATE_CONNECTED, firedFlags, mask)); + } + + /** + * Disconnects a profile between the local device and a remote device and checks to make sure + * that the profile is disconnected and that the correct actions were broadcast. + * + * @param adapter The BT adapter. + * @param device The remote device. + * @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP}, + * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}. + * @param methodName The method name to printed in the logs. If null, will be + * "connectProfile(profile=<profile>, device=<device>)" + */ + public void disconnectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile, + String methodName) { + if (methodName == null) { + methodName = String.format("disconnectProfile(profile=%d, device=%s)", profile, device); + } + int mask = (ConnectProfileReceiver.STATE_DISCONNECTING_FLAG + | ConnectProfileReceiver.STATE_DISCONNECTED_FLAG); + long start = -1; + + if (!adapter.isEnabled()) { + fail(String.format("%s bluetooth not enabled", methodName)); + } + + if (!adapter.getBondedDevices().contains(device)) { + fail(String.format("%s device not paired", methodName)); + } + + BluetoothProfile proxy = connectProxy(adapter, profile); + assertNotNull(proxy); + + ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask); + + int state = proxy.getConnectionState(device); + switch (state) { + case BluetoothProfile.STATE_CONNECTED: + case BluetoothProfile.STATE_CONNECTING: + start = System.currentTimeMillis(); + assertTrue(proxy.disconnect(device)); + break; + case BluetoothProfile.STATE_DISCONNECTED: + removeReceiver(receiver); + return; + case BluetoothProfile.STATE_DISCONNECTING: + mask = 0; // Don't check for received intents since we might have missed them. + break; + default: + removeReceiver(receiver); + fail(String.format("%s invalid state: state=%d", methodName, state)); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { + state = proxy.getConnectionState(device); + if (state == BluetoothProfile.STATE_DISCONNECTED + && (receiver.getFiredFlags() & mask) == mask) { + long finish = receiver.getCompletedTime(); + if (start != -1 && finish != -1) { + writeOutput(String.format("%s completed in %d ms", methodName, + (finish - start))); + } else { + writeOutput(String.format("%s completed", methodName)); + } + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", + methodName, state, BluetoothProfile.STATE_DISCONNECTED, firedFlags, mask)); + } + + /** + * Connects the PANU to a remote NAP and checks to make sure that the PANU is connected and that + * the correct actions were broadcast. + * + * @param adapter The BT adapter. + * @param device The remote device. + */ + public void connectPan(BluetoothAdapter adapter, BluetoothDevice device) { + connectPanOrIncomingPanConnection(adapter, device, true); + } + + /** + * Checks that a remote PANU connects to the local NAP correctly and that the correct actions + * were broadcast. + * + * @param adapter The BT adapter. + * @param device The remote device. + */ + public void incomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device) { + connectPanOrIncomingPanConnection(adapter, device, false); + } + + /** + * Helper method used by {@link #connectPan(BluetoothAdapter, BluetoothDevice)} and + * {@link #incomingPanConnection(BluetoothAdapter, BluetoothDevice)} to either connect to a + * remote NAP or verify that a remote device connected to the local NAP. + * + * @param adapter The BT adapter. + * @param device The remote device. + * @param connect If the method should initiate the connection (is PANU) + */ + private void connectPanOrIncomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device, + boolean connect) { + long start = -1; + int mask, role; + String methodName; + + if (connect) { + methodName = String.format("connectPan(device=%s)", device); + mask = (ConnectProfileReceiver.STATE_CONNECTED_FLAG | + ConnectProfileReceiver.STATE_CONNECTING_FLAG); + role = BluetoothPan.LOCAL_PANU_ROLE; + } else { + methodName = String.format("incomingPanConnection(device=%s)", device); + mask = ConnectProfileReceiver.STATE_CONNECTED_FLAG; + role = BluetoothPan.LOCAL_NAP_ROLE; + } + + if (!adapter.isEnabled()) { + fail(String.format("%s bluetooth not enabled", methodName)); + } + + if (!adapter.getBondedDevices().contains(device)) { + fail(String.format("%s device not paired", methodName)); + } + + mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); + assertNotNull(mPan); + ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask); + + int state = mPan.getConnectionState(device); + switch (state) { + case BluetoothPan.STATE_CONNECTED: + removeReceiver(receiver); + return; + case BluetoothPan.STATE_CONNECTING: + mask = 0; // Don't check for received intents since we might have missed them. + break; + case BluetoothPan.STATE_DISCONNECTED: + case BluetoothPan.STATE_DISCONNECTING: + start = System.currentTimeMillis(); + if (role == BluetoothPan.LOCAL_PANU_ROLE) { + Log.i("BT", "connect to pan"); + assertTrue(mPan.connect(device)); + } + break; + default: + removeReceiver(receiver); + fail(String.format("%s invalid state: state=%d", methodName, state)); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { + state = mPan.getConnectionState(device); + if (state == BluetoothPan.STATE_CONNECTED + && (receiver.getFiredFlags() & mask) == mask) { + long finish = receiver.getCompletedTime(); + if (start != -1 && finish != -1) { + writeOutput(String.format("%s completed in %d ms", methodName, + (finish - start))); + } else { + writeOutput(String.format("%s completed", methodName)); + } + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", + methodName, state, BluetoothPan.STATE_CONNECTED, firedFlags, mask)); + } + + /** + * Disconnects the PANU from a remote NAP and checks to make sure that the PANU is disconnected + * and that the correct actions were broadcast. + * + * @param adapter The BT adapter. + * @param device The remote device. + */ + public void disconnectPan(BluetoothAdapter adapter, BluetoothDevice device) { + disconnectFromRemoteOrVerifyConnectNap(adapter, device, true); + } + + /** + * Checks that a remote PANU disconnects from the local NAP correctly and that the correct + * actions were broadcast. + * + * @param adapter The BT adapter. + * @param device The remote device. + */ + public void incomingPanDisconnection(BluetoothAdapter adapter, BluetoothDevice device) { + disconnectFromRemoteOrVerifyConnectNap(adapter, device, false); + } + + /** + * Helper method used by {@link #disconnectPan(BluetoothAdapter, BluetoothDevice)} and + * {@link #incomingPanDisconnection(BluetoothAdapter, BluetoothDevice)} to either disconnect + * from a remote NAP or verify that a remote device disconnected from the local NAP. + * + * @param adapter The BT adapter. + * @param device The remote device. + * @param disconnect Whether the method should connect or verify. + */ + private void disconnectFromRemoteOrVerifyConnectNap(BluetoothAdapter adapter, + BluetoothDevice device, boolean disconnect) { + long start = -1; + int mask, role; + String methodName; + + if (disconnect) { + methodName = String.format("disconnectPan(device=%s)", device); + mask = (ConnectProfileReceiver.STATE_DISCONNECTED_FLAG | + ConnectProfileReceiver.STATE_DISCONNECTING_FLAG); + role = BluetoothPan.LOCAL_PANU_ROLE; + } else { + methodName = String.format("incomingPanDisconnection(device=%s)", device); + mask = ConnectProfileReceiver.STATE_DISCONNECTED_FLAG; + role = BluetoothPan.LOCAL_NAP_ROLE; + } + + if (!adapter.isEnabled()) { + fail(String.format("%s bluetooth not enabled", methodName)); + } + + if (!adapter.getBondedDevices().contains(device)) { + fail(String.format("%s device not paired", methodName)); + } + + mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); + assertNotNull(mPan); + ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask); + + int state = mPan.getConnectionState(device); + switch (state) { + case BluetoothPan.STATE_CONNECTED: + case BluetoothPan.STATE_CONNECTING: + start = System.currentTimeMillis(); + if (role == BluetoothPan.LOCAL_PANU_ROLE) { + assertTrue(mPan.disconnect(device)); + } + break; + case BluetoothPan.STATE_DISCONNECTED: + removeReceiver(receiver); + return; + case BluetoothPan.STATE_DISCONNECTING: + mask = 0; // Don't check for received intents since we might have missed them. + break; + default: + removeReceiver(receiver); + fail(String.format("%s invalid state: state=%d", methodName, state)); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { + state = mPan.getConnectionState(device); + if (state == BluetoothInputDevice.STATE_DISCONNECTED + && (receiver.getFiredFlags() & mask) == mask) { + long finish = receiver.getCompletedTime(); + if (start != -1 && finish != -1) { + writeOutput(String.format("%s completed in %d ms", methodName, + (finish - start))); + } else { + writeOutput(String.format("%s completed", methodName)); + } + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", + methodName, state, BluetoothInputDevice.STATE_DISCONNECTED, firedFlags, mask)); + } + + /** + * Opens a SCO channel using {@link android.media.AudioManager#startBluetoothSco()} and checks + * to make sure that the channel is opened and that the correct actions were broadcast. + * + * @param adapter The BT adapter. + * @param device The remote device. + */ + public void startSco(BluetoothAdapter adapter, BluetoothDevice device) { + startStopSco(adapter, device, true); + } + + /** + * Closes a SCO channel using {@link android.media.AudioManager#stopBluetoothSco()} and checks + * to make sure that the channel is closed and that the correct actions were broadcast. + * + * @param adapter The BT adapter. + * @param device The remote device. + */ + public void stopSco(BluetoothAdapter adapter, BluetoothDevice device) { + startStopSco(adapter, device, false); + } + /** + * Helper method for {@link #startSco(BluetoothAdapter, BluetoothDevice)} and + * {@link #stopSco(BluetoothAdapter, BluetoothDevice)}. + * + * @param adapter The BT adapter. + * @param device The remote device. + * @param isStart Whether the SCO channel should be opened. + */ + private void startStopSco(BluetoothAdapter adapter, BluetoothDevice device, boolean isStart) { + long start = -1; + int mask; + String methodName; + + if (isStart) { + methodName = String.format("startSco(device=%s)", device); + mask = StartStopScoReceiver.STATE_CONNECTED_FLAG; + } else { + methodName = String.format("stopSco(device=%s)", device); + mask = StartStopScoReceiver.STATE_DISCONNECTED_FLAG; + } + + if (!adapter.isEnabled()) { + fail(String.format("%s bluetooth not enabled", methodName)); + } + + if (!adapter.getBondedDevices().contains(device)) { + fail(String.format("%s device not paired", methodName)); + } + + AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + assertNotNull(manager); + + if (!manager.isBluetoothScoAvailableOffCall()) { + fail(String.format("%s device does not support SCO", methodName)); + } + + boolean isScoOn = manager.isBluetoothScoOn(); + if (isStart == isScoOn) { + return; + } + + StartStopScoReceiver receiver = getStartStopScoReceiver(mask); + start = System.currentTimeMillis(); + if (isStart) { + manager.startBluetoothSco(); + } else { + manager.stopBluetoothSco(); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < START_STOP_SCO_TIMEOUT) { + isScoOn = manager.isBluetoothScoOn(); + if (isStart == isScoOn && (receiver.getFiredFlags() & mask) == mask) { + long finish = receiver.getCompletedTime(); + if (start != -1 && finish != -1) { + writeOutput(String.format("%s completed in %d ms", methodName, + (finish - start))); + } else { + writeOutput(String.format("%s completed", methodName)); + } + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("%s timeout: on=%b (expected %b), flags=0x%x (expected 0x%x)", + methodName, isScoOn, isStart, firedFlags, mask)); + } + + /** + * Writes a string to the logcat and a file if a file has been specified in the constructor. + * + * @param s The string to be written. + */ + public void writeOutput(String s) { + Log.i(mTag, s); + if (mOutputWriter == null) { + return; + } + try { + mOutputWriter.write(s + "\n"); + mOutputWriter.flush(); + } catch (IOException e) { + Log.w(mTag, "Could not write to output file", e); + } + } + + private void addReceiver(BroadcastReceiver receiver, String[] actions) { + IntentFilter filter = new IntentFilter(); + for (String action: actions) { + filter.addAction(action); + } + mContext.registerReceiver(receiver, filter); + mReceivers.add(receiver); + } + + private BluetoothReceiver getBluetoothReceiver(int expectedFlags) { + String[] actions = { + BluetoothAdapter.ACTION_DISCOVERY_FINISHED, + BluetoothAdapter.ACTION_DISCOVERY_STARTED, + BluetoothAdapter.ACTION_SCAN_MODE_CHANGED, + BluetoothAdapter.ACTION_STATE_CHANGED}; + BluetoothReceiver receiver = new BluetoothReceiver(expectedFlags); + addReceiver(receiver, actions); + return receiver; + } + + private PairReceiver getPairReceiver(BluetoothDevice device, int passkey, byte[] pin, + int expectedFlags) { + String[] actions = { + BluetoothDevice.ACTION_PAIRING_REQUEST, + BluetoothDevice.ACTION_BOND_STATE_CHANGED}; + PairReceiver receiver = new PairReceiver(device, passkey, pin, expectedFlags); + addReceiver(receiver, actions); + return receiver; + } + + private ConnectProfileReceiver getConnectProfileReceiver(BluetoothDevice device, int profile, + int expectedFlags) { + String[] actions = { + BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, + BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED, + BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED}; + ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile, + expectedFlags); + addReceiver(receiver, actions); + return receiver; + } + + private ConnectPanReceiver getConnectPanReceiver(BluetoothDevice device, int role, + int expectedFlags) { + String[] actions = {BluetoothPan.ACTION_CONNECTION_STATE_CHANGED}; + ConnectPanReceiver receiver = new ConnectPanReceiver(device, role, expectedFlags); + addReceiver(receiver, actions); + return receiver; + } + + private StartStopScoReceiver getStartStopScoReceiver(int expectedFlags) { + String[] actions = {AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED}; + StartStopScoReceiver receiver = new StartStopScoReceiver(expectedFlags); + addReceiver(receiver, actions); + return receiver; + } + + private void removeReceiver(BroadcastReceiver receiver) { + mContext.unregisterReceiver(receiver); + mReceivers.remove(receiver); + } + + private BluetoothProfile connectProxy(BluetoothAdapter adapter, int profile) { + switch (profile) { + case BluetoothProfile.A2DP: + if (mA2dp != null) { + return mA2dp; + } + break; + case BluetoothProfile.HEADSET: + if (mHeadset != null) { + return mHeadset; + } + break; + case BluetoothProfile.INPUT_DEVICE: + if (mInput != null) { + return mInput; + } + break; + case BluetoothProfile.PAN: + if (mPan != null) { + return mPan; + } + break; + default: + return null; + } + adapter.getProfileProxy(mContext, mServiceListener, profile); + long s = System.currentTimeMillis(); + switch (profile) { + case BluetoothProfile.A2DP: + while (mA2dp == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { + sleep(POLL_TIME); + } + return mA2dp; + case BluetoothProfile.HEADSET: + while (mHeadset == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { + sleep(POLL_TIME); + } + return mHeadset; + case BluetoothProfile.INPUT_DEVICE: + while (mInput == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { + sleep(POLL_TIME); + } + return mInput; + case BluetoothProfile.PAN: + while (mPan == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { + sleep(POLL_TIME); + } + return mPan; + default: + return null; + } + } + + private void sleep(long time) { + try { + Thread.sleep(time); + } catch (InterruptedException e) { + } + } +} |