diff options
4 files changed, 178 insertions, 0 deletions
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 01734f2f755a..4be6995f46f1 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -44,6 +44,7 @@ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.CLEAR_APP_CACHE" /> diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java index 672f252388fd..5dedd4a92b56 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java +++ b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java @@ -316,4 +316,34 @@ public class BluetoothStressTest extends InstrumentationTestCase { 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.sHeadsetAddress); + mTestUtils.enable(adapter); + mTestUtils.pair(adapter, device, BluetoothTestRunner.sPairPasskey, + BluetoothTestRunner.sPairPin); + mTestUtils.connectProfile(adapter, device, BluetoothProfile.HEADSET); + + 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); + mTestUtils.unpair(adapter, device); + mTestUtils.disable(adapter); + } } diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java index cede05a8a174..1febc5cc7beb 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java +++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java @@ -39,6 +39,7 @@ import android.util.Log; * [-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>] \ @@ -62,6 +63,7 @@ public class BluetoothTestRunner extends InstrumentationTestRunner { public static int sConnectA2dpIterations = 100; public static int sConnectInputIterations = 100; public static int sConnectPanIterations = 100; + public static int sStartStopScoIterations = 100; public static String sPairAddress = ""; public static String sHeadsetAddress = ""; @@ -167,6 +169,14 @@ public class BluetoothTestRunner extends InstrumentationTestRunner { } } + 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("pair_address"); if (val != null) { sPairAddress = val; @@ -214,6 +224,7 @@ public class BluetoothTestRunner extends InstrumentationTestRunner { 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("pair_address=%s", sPairAddress)); Log.i(TAG, String.format("a2dp_address=%s", sA2dpAddress)); Log.i(TAG, String.format("headset_address=%s", sHeadsetAddress)); diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java index 85c5eaa85b9b..1741119cad9b 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java +++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java @@ -22,6 +22,7 @@ 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; @@ -67,6 +68,11 @@ public class BluetoothTestUtils extends Assert { private static final int CONNECT_PROXY_TIMEOUT = 5000; /** + * Timeout to start or stop a SCO channel in ms. + */ + private static final int START_STOP_SCO_TIMEOUT = 10000; + + /** * Time between polls in ms. */ private static final int POLL_TIME = 100; @@ -319,6 +325,32 @@ public class BluetoothTestUtils extends Assert { } } + 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() { public void onServiceConnected(int profile, BluetoothProfile proxy) { @@ -1269,6 +1301,103 @@ public class BluetoothTestUtils extends Assert { } /** + * 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 = "startSco()"; + mask = StartStopScoReceiver.STATE_CONNECTED_FLAG; + } else { + methodName = "stopSco()"; + mask = StartStopScoReceiver.STATE_DISCONNECTED_FLAG; + } + + if (!adapter.isEnabled()) { + fail(String.format("%s bluetooth not enabled: device=%s, start=%b", methodName, device, + isStart)); + } + + if (!adapter.getBondedDevices().contains(device)) { + fail(String.format("%s device not paired: device=%s, start=%b", methodName, device, + isStart)); + } + + AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + assertNotNull(manager); + + if (!manager.isBluetoothScoAvailableOffCall()) { + fail(String.format("%s device does not support SCO: device=%s, start=%b", methodName, + device, isStart)); + } + + 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: start=%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. @@ -1336,6 +1465,13 @@ public class BluetoothTestUtils extends Assert { 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); |