BluetoothTestUtils.java revision b525f089cf0f580ccb26e80bbb0db1376ba554cc
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.bluetooth;
18
19import android.bluetooth.BluetoothPan;
20import android.bluetooth.BluetoothProfile;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.media.AudioManager;
26import android.os.Environment;
27import android.util.Log;
28
29import junit.framework.Assert;
30
31import java.io.BufferedWriter;
32import java.io.File;
33import java.io.FileWriter;
34import java.io.IOException;
35import java.util.ArrayList;
36import java.util.List;
37
38public class BluetoothTestUtils extends Assert {
39
40    /** Timeout for enable/disable in ms. */
41    private static final int ENABLE_DISABLE_TIMEOUT = 20000;
42    /** Timeout for discoverable/undiscoverable in ms. */
43    private static final int DISCOVERABLE_UNDISCOVERABLE_TIMEOUT = 5000;
44    /** Timeout for starting/stopping a scan in ms. */
45    private static final int START_STOP_SCAN_TIMEOUT = 5000;
46    /** Timeout for pair/unpair in ms. */
47    private static final int PAIR_UNPAIR_TIMEOUT = 20000;
48    /** Timeout for connecting/disconnecting a profile in ms. */
49    private static final int CONNECT_DISCONNECT_PROFILE_TIMEOUT = 20000;
50    /** Timeout to start or stop a SCO channel in ms. */
51    private static final int START_STOP_SCO_TIMEOUT = 10000;
52    /** Timeout to connect a profile proxy in ms. */
53    private static final int CONNECT_PROXY_TIMEOUT = 5000;
54    /** Time between polls in ms. */
55    private static final int POLL_TIME = 100;
56
57    private abstract class FlagReceiver extends BroadcastReceiver {
58        private int mExpectedFlags = 0;
59        private int mFiredFlags = 0;
60        private long mCompletedTime = -1;
61
62        public FlagReceiver(int expectedFlags) {
63            mExpectedFlags = expectedFlags;
64        }
65
66        public int getFiredFlags() {
67            synchronized (this) {
68                return mFiredFlags;
69            }
70        }
71
72        public long getCompletedTime() {
73            synchronized (this) {
74                return mCompletedTime;
75            }
76        }
77
78        protected void setFiredFlag(int flag) {
79            synchronized (this) {
80                mFiredFlags |= flag;
81                if ((mFiredFlags & mExpectedFlags) == mExpectedFlags) {
82                    mCompletedTime = System.currentTimeMillis();
83                }
84            }
85        }
86    }
87
88    private class BluetoothReceiver extends FlagReceiver {
89        private static final int DISCOVERY_STARTED_FLAG = 1;
90        private static final int DISCOVERY_FINISHED_FLAG = 1 << 1;
91        private static final int SCAN_MODE_NONE_FLAG = 1 << 2;
92        private static final int SCAN_MODE_CONNECTABLE_FLAG = 1 << 3;
93        private static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG = 1 << 4;
94        private static final int STATE_OFF_FLAG = 1 << 5;
95        private static final int STATE_TURNING_ON_FLAG = 1 << 6;
96        private static final int STATE_ON_FLAG = 1 << 7;
97        private static final int STATE_TURNING_OFF_FLAG = 1 << 8;
98
99        public BluetoothReceiver(int expectedFlags) {
100            super(expectedFlags);
101        }
102
103        @Override
104        public void onReceive(Context context, Intent intent) {
105            if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) {
106                setFiredFlag(DISCOVERY_STARTED_FLAG);
107            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) {
108                setFiredFlag(DISCOVERY_FINISHED_FLAG);
109            } else if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) {
110                int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, -1);
111                assertNotSame(-1, mode);
112                switch (mode) {
113                    case BluetoothAdapter.SCAN_MODE_NONE:
114                        setFiredFlag(SCAN_MODE_NONE_FLAG);
115                        break;
116                    case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
117                        setFiredFlag(SCAN_MODE_CONNECTABLE_FLAG);
118                        break;
119                    case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
120                        setFiredFlag(SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG);
121                        break;
122                }
123            } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
124                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
125                assertNotSame(-1, state);
126                switch (state) {
127                    case BluetoothAdapter.STATE_OFF:
128                        setFiredFlag(STATE_OFF_FLAG);
129                        break;
130                    case BluetoothAdapter.STATE_TURNING_ON:
131                        setFiredFlag(STATE_TURNING_ON_FLAG);
132                        break;
133                    case BluetoothAdapter.STATE_ON:
134                        setFiredFlag(STATE_ON_FLAG);
135                        break;
136                    case BluetoothAdapter.STATE_TURNING_OFF:
137                        setFiredFlag(STATE_TURNING_OFF_FLAG);
138                        break;
139                }
140            }
141        }
142    }
143
144    private class PairReceiver extends FlagReceiver {
145        private static final int STATE_BONDED_FLAG = 1;
146        private static final int STATE_BONDING_FLAG = 1 << 1;
147        private static final int STATE_NONE_FLAG = 1 << 2;
148
149        private BluetoothDevice mDevice;
150        private int mPasskey;
151        private byte[] mPin;
152
153        public PairReceiver(BluetoothDevice device, int passkey, byte[] pin, int expectedFlags) {
154            super(expectedFlags);
155
156            mDevice = device;
157            mPasskey = passkey;
158            mPin = pin;
159        }
160
161        @Override
162        public void onReceive(Context context, Intent intent) {
163            if (!mDevice.equals(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))) {
164                return;
165            }
166
167            if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) {
168                int varient = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, -1);
169                assertNotSame(-1, varient);
170                switch (varient) {
171                    case BluetoothDevice.PAIRING_VARIANT_PIN:
172                        mDevice.setPin(mPin);
173                        break;
174                    case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
175                        mDevice.setPasskey(mPasskey);
176                        break;
177                    case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
178                    case BluetoothDevice.PAIRING_VARIANT_CONSENT:
179                        mDevice.setPairingConfirmation(true);
180                        break;
181                    case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
182                        mDevice.setRemoteOutOfBandData();
183                        break;
184                }
185            } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
186                int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
187                assertNotSame(-1, state);
188                switch (state) {
189                    case BluetoothDevice.BOND_NONE:
190                        setFiredFlag(STATE_NONE_FLAG);
191                        break;
192                    case BluetoothDevice.BOND_BONDING:
193                        setFiredFlag(STATE_BONDING_FLAG);
194                        break;
195                    case BluetoothDevice.BOND_BONDED:
196                        setFiredFlag(STATE_BONDED_FLAG);
197                        break;
198                }
199            }
200        }
201    }
202
203    private class ConnectProfileReceiver extends FlagReceiver {
204        private static final int STATE_DISCONNECTED_FLAG = 1;
205        private static final int STATE_CONNECTING_FLAG = 1 << 1;
206        private static final int STATE_CONNECTED_FLAG = 1 << 2;
207        private static final int STATE_DISCONNECTING_FLAG = 1 << 3;
208
209        private BluetoothDevice mDevice;
210        private int mProfile;
211        private String mConnectionAction;
212
213        public ConnectProfileReceiver(BluetoothDevice device, int profile, int expectedFlags) {
214            super(expectedFlags);
215
216            mDevice = device;
217            mProfile = profile;
218
219            switch (mProfile) {
220                case BluetoothProfile.A2DP:
221                    mConnectionAction = BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED;
222                    break;
223                case BluetoothProfile.HEADSET:
224                    mConnectionAction = BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED;
225                    break;
226                case BluetoothProfile.INPUT_DEVICE:
227                    mConnectionAction = BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED;
228                    break;
229                case BluetoothProfile.PAN:
230                    mConnectionAction = BluetoothPan.ACTION_CONNECTION_STATE_CHANGED;
231                    break;
232                default:
233                    mConnectionAction = null;
234            }
235        }
236
237        @Override
238        public void onReceive(Context context, Intent intent) {
239            if (mConnectionAction != null && mConnectionAction.equals(intent.getAction())) {
240                if (!mDevice.equals(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))) {
241                    return;
242                }
243
244                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
245                assertNotSame(-1, state);
246                switch (state) {
247                    case BluetoothProfile.STATE_DISCONNECTED:
248                        setFiredFlag(STATE_DISCONNECTED_FLAG);
249                        break;
250                    case BluetoothProfile.STATE_CONNECTING:
251                        setFiredFlag(STATE_CONNECTING_FLAG);
252                        break;
253                    case BluetoothProfile.STATE_CONNECTED:
254                        setFiredFlag(STATE_CONNECTED_FLAG);
255                        break;
256                    case BluetoothProfile.STATE_DISCONNECTING:
257                        setFiredFlag(STATE_DISCONNECTING_FLAG);
258                        break;
259                }
260            }
261        }
262    }
263
264    private class ConnectPanReceiver extends ConnectProfileReceiver {
265        private int mRole;
266
267        public ConnectPanReceiver(BluetoothDevice device, int role, int expectedFlags) {
268            super(device, BluetoothProfile.PAN, expectedFlags);
269
270            mRole = role;
271        }
272
273        @Override
274        public void onReceive(Context context, Intent intent) {
275            if (mRole != intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, -1)) {
276                return;
277            }
278
279            super.onReceive(context, intent);
280        }
281    }
282
283    private class StartStopScoReceiver extends FlagReceiver {
284        private static final int STATE_CONNECTED_FLAG = 1;
285        private static final int STATE_DISCONNECTED_FLAG = 1 << 1;
286
287        public StartStopScoReceiver(int expectedFlags) {
288            super(expectedFlags);
289        }
290
291        @Override
292        public void onReceive(Context context, Intent intent) {
293            if (AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED.equals(intent.getAction())) {
294                int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
295                        AudioManager.SCO_AUDIO_STATE_ERROR);
296                assertNotSame(AudioManager.SCO_AUDIO_STATE_ERROR, state);
297                switch(state) {
298                    case AudioManager.SCO_AUDIO_STATE_CONNECTED:
299                        setFiredFlag(STATE_CONNECTED_FLAG);
300                        break;
301                    case AudioManager.SCO_AUDIO_STATE_DISCONNECTED:
302                        setFiredFlag(STATE_DISCONNECTED_FLAG);
303                        break;
304                }
305            }
306        }
307    }
308
309    private BluetoothProfile.ServiceListener mServiceListener =
310            new BluetoothProfile.ServiceListener() {
311        @Override
312        public void onServiceConnected(int profile, BluetoothProfile proxy) {
313            synchronized (this) {
314                switch (profile) {
315                    case BluetoothProfile.A2DP:
316                        mA2dp = (BluetoothA2dp) proxy;
317                        break;
318                    case BluetoothProfile.HEADSET:
319                        mHeadset = (BluetoothHeadset) proxy;
320                        break;
321                    case BluetoothProfile.INPUT_DEVICE:
322                        mInput = (BluetoothInputDevice) proxy;
323                        break;
324                    case BluetoothProfile.PAN:
325                        mPan = (BluetoothPan) proxy;
326                        break;
327                }
328            }
329        }
330
331        @Override
332        public void onServiceDisconnected(int profile) {
333            synchronized (this) {
334                switch (profile) {
335                    case BluetoothProfile.A2DP:
336                        mA2dp = null;
337                        break;
338                    case BluetoothProfile.HEADSET:
339                        mHeadset = null;
340                        break;
341                    case BluetoothProfile.INPUT_DEVICE:
342                        mInput = null;
343                        break;
344                    case BluetoothProfile.PAN:
345                        mPan = null;
346                        break;
347                }
348            }
349        }
350    };
351
352    private List<BroadcastReceiver> mReceivers = new ArrayList<BroadcastReceiver>();
353
354    private BufferedWriter mOutputWriter;
355    private String mTag;
356    private String mOutputFile;
357
358    private Context mContext;
359    private BluetoothA2dp mA2dp = null;
360    private BluetoothHeadset mHeadset = null;
361    private BluetoothInputDevice mInput = null;
362    private BluetoothPan mPan = null;
363
364    /**
365     * Creates a utility instance for testing Bluetooth.
366     *
367     * @param context The context of the application using the utility.
368     * @param tag The log tag of the application using the utility.
369     */
370    public BluetoothTestUtils(Context context, String tag) {
371        this(context, tag, null);
372    }
373
374    /**
375     * Creates a utility instance for testing Bluetooth.
376     *
377     * @param context The context of the application using the utility.
378     * @param tag The log tag of the application using the utility.
379     * @param outputFile The path to an output file if the utility is to write results to a
380     *        separate file.
381     */
382    public BluetoothTestUtils(Context context, String tag, String outputFile) {
383        mContext = context;
384        mTag = tag;
385        mOutputFile = outputFile;
386
387        if (mOutputFile == null) {
388            mOutputWriter = null;
389        } else {
390            try {
391                mOutputWriter = new BufferedWriter(new FileWriter(new File(
392                        Environment.getExternalStorageDirectory(), mOutputFile), true));
393            } catch (IOException e) {
394                Log.w(mTag, "Test output file could not be opened", e);
395                mOutputWriter = null;
396            }
397        }
398    }
399
400    /**
401     * Closes the utility instance and unregisters any BroadcastReceivers.
402     */
403    public void close() {
404        while (!mReceivers.isEmpty()) {
405            mContext.unregisterReceiver(mReceivers.remove(0));
406        }
407
408        if (mOutputWriter != null) {
409            try {
410                mOutputWriter.close();
411            } catch (IOException e) {
412                Log.w(mTag, "Test output file could not be closed", e);
413            }
414        }
415    }
416
417    /**
418     * Enables Bluetooth and checks to make sure that Bluetooth was turned on and that the correct
419     * actions were broadcast.
420     *
421     * @param adapter The BT adapter.
422     */
423    public void enable(BluetoothAdapter adapter) {
424        int mask = (BluetoothReceiver.STATE_TURNING_ON_FLAG | BluetoothReceiver.STATE_ON_FLAG
425                | BluetoothReceiver.SCAN_MODE_CONNECTABLE_FLAG);
426        long start = -1;
427        BluetoothReceiver receiver = getBluetoothReceiver(mask);
428
429        int state = adapter.getState();
430        switch (state) {
431            case BluetoothAdapter.STATE_ON:
432                assertTrue(adapter.isEnabled());
433                removeReceiver(receiver);
434                return;
435            case BluetoothAdapter.STATE_TURNING_ON:
436                assertFalse(adapter.isEnabled());
437                mask = 0; // Don't check for received intents since we might have missed them.
438                break;
439            case BluetoothAdapter.STATE_OFF:
440                assertFalse(adapter.isEnabled());
441                start = System.currentTimeMillis();
442                assertTrue(adapter.enable());
443                break;
444            case BluetoothAdapter.STATE_TURNING_OFF:
445                start = System.currentTimeMillis();
446                assertTrue(adapter.enable());
447                break;
448            default:
449                removeReceiver(receiver);
450                fail(String.format("enable() invalid state: state=%d", state));
451        }
452
453        long s = System.currentTimeMillis();
454        while (System.currentTimeMillis() - s < ENABLE_DISABLE_TIMEOUT) {
455            state = adapter.getState();
456            if (state == BluetoothAdapter.STATE_ON
457                    && (receiver.getFiredFlags() & mask) == mask) {
458                assertTrue(adapter.isEnabled());
459                long finish = receiver.getCompletedTime();
460                if (start != -1 && finish != -1) {
461                    writeOutput(String.format("enable() completed in %d ms", (finish - start)));
462                } else {
463                    writeOutput("enable() completed");
464                }
465                removeReceiver(receiver);
466                return;
467            }
468            sleep(POLL_TIME);
469        }
470
471        int firedFlags = receiver.getFiredFlags();
472        removeReceiver(receiver);
473        fail(String.format("enable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
474                state, BluetoothAdapter.STATE_ON, firedFlags, mask));
475    }
476
477    /**
478     * Disables Bluetooth and checks to make sure that Bluetooth was turned off and that the correct
479     * actions were broadcast.
480     *
481     * @param adapter The BT adapter.
482     */
483    public void disable(BluetoothAdapter adapter) {
484        int mask = (BluetoothReceiver.STATE_TURNING_OFF_FLAG | BluetoothReceiver.STATE_OFF_FLAG
485                | BluetoothReceiver.SCAN_MODE_NONE_FLAG);
486        long start = -1;
487        BluetoothReceiver receiver = getBluetoothReceiver(mask);
488
489        int state = adapter.getState();
490        switch (state) {
491            case BluetoothAdapter.STATE_OFF:
492                assertFalse(adapter.isEnabled());
493                removeReceiver(receiver);
494                return;
495            case BluetoothAdapter.STATE_TURNING_ON:
496                assertFalse(adapter.isEnabled());
497                start = System.currentTimeMillis();
498                break;
499            case BluetoothAdapter.STATE_ON:
500                assertTrue(adapter.isEnabled());
501                start = System.currentTimeMillis();
502                assertTrue(adapter.disable());
503                break;
504            case BluetoothAdapter.STATE_TURNING_OFF:
505                assertFalse(adapter.isEnabled());
506                mask = 0; // Don't check for received intents since we might have missed them.
507                break;
508            default:
509                removeReceiver(receiver);
510                fail(String.format("disable() invalid state: state=%d", state));
511        }
512
513        long s = System.currentTimeMillis();
514        while (System.currentTimeMillis() - s < ENABLE_DISABLE_TIMEOUT) {
515            state = adapter.getState();
516            if (state == BluetoothAdapter.STATE_OFF
517                    && (receiver.getFiredFlags() & mask) == mask) {
518                assertFalse(adapter.isEnabled());
519                long finish = receiver.getCompletedTime();
520                if (start != -1 && finish != -1) {
521                    writeOutput(String.format("disable() completed in %d ms", (finish - start)));
522                } else {
523                    writeOutput("disable() completed");
524                }
525                removeReceiver(receiver);
526                return;
527            }
528            sleep(POLL_TIME);
529        }
530
531        int firedFlags = receiver.getFiredFlags();
532        removeReceiver(receiver);
533        fail(String.format("disable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
534                state, BluetoothAdapter.STATE_OFF, firedFlags, mask));
535    }
536
537    /**
538     * Puts the local device into discoverable mode and checks to make sure that the local device
539     * is in discoverable mode and that the correct actions were broadcast.
540     *
541     * @param adapter The BT adapter.
542     */
543    public void discoverable(BluetoothAdapter adapter) {
544        int mask = BluetoothReceiver.SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG;
545
546        if (!adapter.isEnabled()) {
547            fail("discoverable() bluetooth not enabled");
548        }
549
550        int scanMode = adapter.getScanMode();
551        if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
552            return;
553        }
554
555        BluetoothReceiver receiver = getBluetoothReceiver(mask);
556
557        assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE, scanMode);
558        long start = System.currentTimeMillis();
559        assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE));
560
561        while (System.currentTimeMillis() - start < DISCOVERABLE_UNDISCOVERABLE_TIMEOUT) {
562            scanMode = adapter.getScanMode();
563            if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
564                    && (receiver.getFiredFlags() & mask) == mask) {
565                writeOutput(String.format("discoverable() completed in %d ms",
566                        (receiver.getCompletedTime() - start)));
567                removeReceiver(receiver);
568                return;
569            }
570            sleep(POLL_TIME);
571        }
572
573        int firedFlags = receiver.getFiredFlags();
574        removeReceiver(receiver);
575        fail(String.format("discoverable() timeout: scanMode=%d (expected %d), flags=0x%x "
576                + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,
577                firedFlags, mask));
578    }
579
580    /**
581     * Puts the local device into connectable only mode and checks to make sure that the local
582     * device is in in connectable mode and that the correct actions were broadcast.
583     *
584     * @param adapter The BT adapter.
585     */
586    public void undiscoverable(BluetoothAdapter adapter) {
587        int mask = BluetoothReceiver.SCAN_MODE_CONNECTABLE_FLAG;
588
589        if (!adapter.isEnabled()) {
590            fail("undiscoverable() bluetooth not enabled");
591        }
592
593        int scanMode = adapter.getScanMode();
594        if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) {
595            return;
596        }
597
598        BluetoothReceiver receiver = getBluetoothReceiver(mask);
599
600        assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, scanMode);
601        long start = System.currentTimeMillis();
602        assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE));
603
604        while (System.currentTimeMillis() - start < DISCOVERABLE_UNDISCOVERABLE_TIMEOUT) {
605            scanMode = adapter.getScanMode();
606            if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE
607                    && (receiver.getFiredFlags() & mask) == mask) {
608                writeOutput(String.format("undiscoverable() completed in %d ms",
609                        (receiver.getCompletedTime() - start)));
610                removeReceiver(receiver);
611                return;
612            }
613            sleep(POLL_TIME);
614        }
615
616        int firedFlags = receiver.getFiredFlags();
617        removeReceiver(receiver);
618        fail(String.format("undiscoverable() timeout: scanMode=%d (expected %d), flags=0x%x "
619                + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE, firedFlags,
620                mask));
621    }
622
623    /**
624     * Starts a scan for remote devices and checks to make sure that the local device is scanning
625     * and that the correct actions were broadcast.
626     *
627     * @param adapter The BT adapter.
628     */
629    public void startScan(BluetoothAdapter adapter) {
630        int mask = BluetoothReceiver.DISCOVERY_STARTED_FLAG;
631
632        if (!adapter.isEnabled()) {
633            fail("startScan() bluetooth not enabled");
634        }
635
636        if (adapter.isDiscovering()) {
637            return;
638        }
639
640        BluetoothReceiver receiver = getBluetoothReceiver(mask);
641
642        long start = System.currentTimeMillis();
643        assertTrue(adapter.startDiscovery());
644
645        while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) {
646            if (adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) {
647                writeOutput(String.format("startScan() completed in %d ms",
648                        (receiver.getCompletedTime() - start)));
649                removeReceiver(receiver);
650                return;
651            }
652            sleep(POLL_TIME);
653        }
654
655        int firedFlags = receiver.getFiredFlags();
656        removeReceiver(receiver);
657        fail(String.format("startScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)",
658                adapter.isDiscovering(), firedFlags, mask));
659    }
660
661    /**
662     * Stops a scan for remote devices and checks to make sure that the local device is not scanning
663     * and that the correct actions were broadcast.
664     *
665     * @param adapter The BT adapter.
666     */
667    public void stopScan(BluetoothAdapter adapter) {
668        int mask = BluetoothReceiver.DISCOVERY_FINISHED_FLAG;
669
670        if (!adapter.isEnabled()) {
671            fail("stopScan() bluetooth not enabled");
672        }
673
674        if (!adapter.isDiscovering()) {
675            return;
676        }
677
678        BluetoothReceiver receiver = getBluetoothReceiver(mask);
679
680        long start = System.currentTimeMillis();
681        // TODO: put assertTrue() around cancelDiscovery() once it starts returning true.
682        adapter.cancelDiscovery();
683
684        while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) {
685            if (!adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) {
686                writeOutput(String.format("stopScan() completed in %d ms",
687                        (receiver.getCompletedTime() - start)));
688                removeReceiver(receiver);
689                return;
690            }
691            sleep(POLL_TIME);
692        }
693
694        int firedFlags = receiver.getFiredFlags();
695        removeReceiver(receiver);
696        fail(String.format("stopScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)",
697                adapter.isDiscovering(), firedFlags, mask));
698
699    }
700
701    /**
702     * Enables PAN tethering on the local device and checks to make sure that tethering is enabled.
703     *
704     * @param adapter The BT adapter.
705     */
706    public void enablePan(BluetoothAdapter adapter) {
707        if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN);
708        assertNotNull(mPan);
709
710        long start = System.currentTimeMillis();
711        mPan.setBluetoothTethering(true);
712        long stop = System.currentTimeMillis();
713        assertTrue(mPan.isTetheringOn());
714
715        writeOutput(String.format("enablePan() completed in %d ms", (stop - start)));
716    }
717
718    /**
719     * Disables PAN tethering on the local device and checks to make sure that tethering is
720     * disabled.
721     *
722     * @param adapter The BT adapter.
723     */
724    public void disablePan(BluetoothAdapter adapter) {
725        if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN);
726        assertNotNull(mPan);
727
728        long start = System.currentTimeMillis();
729        mPan.setBluetoothTethering(false);
730        long stop = System.currentTimeMillis();
731        assertFalse(mPan.isTetheringOn());
732
733        writeOutput(String.format("disablePan() completed in %d ms", (stop - start)));
734    }
735
736    /**
737     * Initiates a pairing with a remote device and checks to make sure that the devices are paired
738     * and that the correct actions were broadcast.
739     *
740     * @param adapter The BT adapter.
741     * @param device The remote device.
742     * @param passkey The pairing passkey if pairing requires a passkey. Any value if not.
743     * @param pin The pairing pin if pairing requires a pin. Any value if not.
744     */
745    public void pair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, byte[] pin) {
746        pairOrAcceptPair(adapter, device, passkey, pin, true);
747    }
748
749    /**
750     * Accepts a pairing with a remote device and checks to make sure that the devices are paired
751     * and that the correct actions were broadcast.
752     *
753     * @param adapter The BT adapter.
754     * @param device The remote device.
755     * @param passkey The pairing passkey if pairing requires a passkey. Any value if not.
756     * @param pin The pairing pin if pairing requires a pin. Any value if not.
757     */
758    public void acceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey,
759            byte[] pin) {
760        pairOrAcceptPair(adapter, device, passkey, pin, false);
761    }
762
763    /**
764     * Helper method used by {@link #pair(BluetoothAdapter, BluetoothDevice, int, byte[])} and
765     * {@link #acceptPair(BluetoothAdapter, BluetoothDevice, int, byte[])} to either pair or accept
766     * a pairing request.
767     *
768     * @param adapter The BT adapter.
769     * @param device The remote device.
770     * @param passkey The pairing passkey if pairing requires a passkey. Any value if not.
771     * @param pin The pairing pin if pairing requires a pin. Any value if not.
772     * @param shouldPair Whether to pair or accept the pair.
773     */
774    private void pairOrAcceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey,
775            byte[] pin, boolean shouldPair) {
776        int mask = PairReceiver.STATE_BONDING_FLAG | PairReceiver.STATE_BONDED_FLAG;
777        long start = -1;
778        String methodName;
779        if (shouldPair) {
780            methodName = String.format("pair(device=%s)", device);
781        } else {
782            methodName = String.format("acceptPair(device=%s)", device);
783        }
784
785        if (!adapter.isEnabled()) {
786            fail(String.format("%s bluetooth not enabled", methodName));
787        }
788
789        PairReceiver receiver = getPairReceiver(device, passkey, pin, mask);
790
791        int state = device.getBondState();
792        switch (state) {
793            case BluetoothDevice.BOND_NONE:
794                assertFalse(adapter.getBondedDevices().contains(device));
795                start = System.currentTimeMillis();
796                if (shouldPair) {
797                    assertTrue(device.createBond());
798                }
799                break;
800            case BluetoothDevice.BOND_BONDING:
801                mask = 0; // Don't check for received intents since we might have missed them.
802                break;
803            case BluetoothDevice.BOND_BONDED:
804                assertTrue(adapter.getBondedDevices().contains(device));
805                return;
806            default:
807                removeReceiver(receiver);
808                fail(String.format("%s invalid state: state=%d", methodName, state));
809        }
810
811        long s = System.currentTimeMillis();
812        while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) {
813            state = device.getBondState();
814            if (state == BluetoothDevice.BOND_BONDED && (receiver.getFiredFlags() & mask) == mask) {
815                assertTrue(adapter.getBondedDevices().contains(device));
816                long finish = receiver.getCompletedTime();
817                if (start != -1 && finish != -1) {
818                    writeOutput(String.format("%s completed in %d ms", methodName,
819                            (finish - start)));
820                } else {
821                    writeOutput(String.format("%s completed", methodName));
822                }
823                removeReceiver(receiver);
824                return;
825            }
826            sleep(POLL_TIME);
827        }
828
829        int firedFlags = receiver.getFiredFlags();
830        removeReceiver(receiver);
831        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
832                methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask));
833    }
834
835    /**
836     * Deletes a pairing with a remote device and checks to make sure that the devices are unpaired
837     * and that the correct actions were broadcast.
838     *
839     * @param adapter The BT adapter.
840     * @param device The remote device.
841     */
842    public void unpair(BluetoothAdapter adapter, BluetoothDevice device) {
843        int mask = PairReceiver.STATE_NONE_FLAG;
844        long start = -1;
845        String methodName = String.format("unpair(device=%s)", device);
846
847        if (!adapter.isEnabled()) {
848            fail(String.format("%s bluetooth not enabled", methodName));
849        }
850
851        PairReceiver receiver = getPairReceiver(device, 0, null, mask);
852
853        int state = device.getBondState();
854        switch (state) {
855            case BluetoothDevice.BOND_NONE:
856                assertFalse(adapter.getBondedDevices().contains(device));
857                removeReceiver(receiver);
858                return;
859            case BluetoothDevice.BOND_BONDING:
860                start = System.currentTimeMillis();
861                assertTrue(device.removeBond());
862                break;
863            case BluetoothDevice.BOND_BONDED:
864                assertTrue(adapter.getBondedDevices().contains(device));
865                start = System.currentTimeMillis();
866                assertTrue(device.removeBond());
867                break;
868            default:
869                removeReceiver(receiver);
870                fail(String.format("%s invalid state: state=%d", methodName, state));
871        }
872
873        long s = System.currentTimeMillis();
874        while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) {
875            if (device.getBondState() == BluetoothDevice.BOND_NONE
876                    && (receiver.getFiredFlags() & mask) == mask) {
877                assertFalse(adapter.getBondedDevices().contains(device));
878                long finish = receiver.getCompletedTime();
879                if (start != -1 && finish != -1) {
880                    writeOutput(String.format("%s completed in %d ms", methodName,
881                            (finish - start)));
882                } else {
883                    writeOutput(String.format("%s completed", methodName));
884                }
885                removeReceiver(receiver);
886                return;
887            }
888        }
889
890        int firedFlags = receiver.getFiredFlags();
891        removeReceiver(receiver);
892        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
893                methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask));
894    }
895
896    /**
897     * Connects a profile from the local device to a remote device and checks to make sure that the
898     * profile is connected and that the correct actions were broadcast.
899     *
900     * @param adapter The BT adapter.
901     * @param device The remote device.
902     * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP},
903     * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}.
904     * @param methodName The method name to printed in the logs.  If null, will be
905     * "connectProfile(profile=&lt;profile&gt;, device=&lt;device&gt;)"
906     */
907    public void connectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile,
908            String methodName) {
909        if (methodName == null) {
910            methodName = String.format("connectProfile(profile=%d, device=%s)", profile, device);
911        }
912        int mask = (ConnectProfileReceiver.STATE_CONNECTING_FLAG
913                | ConnectProfileReceiver.STATE_CONNECTED_FLAG);
914        long start = -1;
915
916        if (!adapter.isEnabled()) {
917            fail(String.format("%s bluetooth not enabled", methodName));
918        }
919
920        if (!adapter.getBondedDevices().contains(device)) {
921            fail(String.format("%s device not paired", methodName));
922        }
923
924        BluetoothProfile proxy = connectProxy(adapter, profile);
925        assertNotNull(proxy);
926
927        ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask);
928
929        int state = proxy.getConnectionState(device);
930        switch (state) {
931            case BluetoothProfile.STATE_CONNECTED:
932                removeReceiver(receiver);
933                return;
934            case BluetoothProfile.STATE_CONNECTING:
935                mask = 0; // Don't check for received intents since we might have missed them.
936                break;
937            case BluetoothProfile.STATE_DISCONNECTED:
938            case BluetoothProfile.STATE_DISCONNECTING:
939                start = System.currentTimeMillis();
940                assertTrue(proxy.connect(device));
941                break;
942            default:
943                removeReceiver(receiver);
944                fail(String.format("%s invalid state: state=%d", methodName, state));
945        }
946
947        long s = System.currentTimeMillis();
948        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
949            state = proxy.getConnectionState(device);
950            if (state == BluetoothProfile.STATE_CONNECTED
951                    && (receiver.getFiredFlags() & mask) == mask) {
952                long finish = receiver.getCompletedTime();
953                if (start != -1 && finish != -1) {
954                    writeOutput(String.format("%s completed in %d ms", methodName,
955                            (finish - start)));
956                } else {
957                    writeOutput(String.format("%s completed", methodName));
958                }
959                removeReceiver(receiver);
960                return;
961            }
962            sleep(POLL_TIME);
963        }
964
965        int firedFlags = receiver.getFiredFlags();
966        removeReceiver(receiver);
967        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
968                methodName, state, BluetoothProfile.STATE_CONNECTED, firedFlags, mask));
969    }
970
971    /**
972     * Disconnects a profile between the local device and a remote device and checks to make sure
973     * that the profile is disconnected and that the correct actions were broadcast.
974     *
975     * @param adapter The BT adapter.
976     * @param device The remote device.
977     * @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP},
978     * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}.
979     * @param methodName The method name to printed in the logs.  If null, will be
980     * "connectProfile(profile=&lt;profile&gt;, device=&lt;device&gt;)"
981     */
982    public void disconnectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile,
983            String methodName) {
984        if (methodName == null) {
985            methodName = String.format("disconnectProfile(profile=%d, device=%s)", profile, device);
986        }
987        int mask = (ConnectProfileReceiver.STATE_DISCONNECTING_FLAG
988                | ConnectProfileReceiver.STATE_DISCONNECTED_FLAG);
989        long start = -1;
990
991        if (!adapter.isEnabled()) {
992            fail(String.format("%s bluetooth not enabled", methodName));
993        }
994
995        if (!adapter.getBondedDevices().contains(device)) {
996            fail(String.format("%s device not paired", methodName));
997        }
998
999        BluetoothProfile proxy = connectProxy(adapter, profile);
1000        assertNotNull(proxy);
1001
1002        ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask);
1003
1004        int state = proxy.getConnectionState(device);
1005        switch (state) {
1006            case BluetoothProfile.STATE_CONNECTED:
1007            case BluetoothProfile.STATE_CONNECTING:
1008                start = System.currentTimeMillis();
1009                assertTrue(proxy.disconnect(device));
1010                break;
1011            case BluetoothProfile.STATE_DISCONNECTED:
1012                removeReceiver(receiver);
1013                return;
1014            case BluetoothProfile.STATE_DISCONNECTING:
1015                mask = 0; // Don't check for received intents since we might have missed them.
1016                break;
1017            default:
1018                removeReceiver(receiver);
1019                fail(String.format("%s invalid state: state=%d", methodName, state));
1020        }
1021
1022        long s = System.currentTimeMillis();
1023        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
1024            state = proxy.getConnectionState(device);
1025            if (state == BluetoothProfile.STATE_DISCONNECTED
1026                    && (receiver.getFiredFlags() & mask) == mask) {
1027                long finish = receiver.getCompletedTime();
1028                if (start != -1 && finish != -1) {
1029                    writeOutput(String.format("%s completed in %d ms", methodName,
1030                            (finish - start)));
1031                } else {
1032                    writeOutput(String.format("%s completed", methodName));
1033                }
1034                removeReceiver(receiver);
1035                return;
1036            }
1037            sleep(POLL_TIME);
1038        }
1039
1040        int firedFlags = receiver.getFiredFlags();
1041        removeReceiver(receiver);
1042        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
1043                methodName, state, BluetoothProfile.STATE_DISCONNECTED, firedFlags, mask));
1044    }
1045
1046    /**
1047     * Connects the PANU to a remote NAP and checks to make sure that the PANU is connected and that
1048     * the correct actions were broadcast.
1049     *
1050     * @param adapter The BT adapter.
1051     * @param device The remote device.
1052     */
1053    public void connectPan(BluetoothAdapter adapter, BluetoothDevice device) {
1054        connectPanOrIncomingPanConnection(adapter, device, true);
1055    }
1056
1057    /**
1058     * Checks that a remote PANU connects to the local NAP correctly and that the correct actions
1059     * were broadcast.
1060     *
1061     * @param adapter The BT adapter.
1062     * @param device The remote device.
1063     */
1064    public void incomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device) {
1065        connectPanOrIncomingPanConnection(adapter, device, false);
1066    }
1067
1068    /**
1069     * Helper method used by {@link #connectPan(BluetoothAdapter, BluetoothDevice)} and
1070     * {@link #incomingPanConnection(BluetoothAdapter, BluetoothDevice)} to either connect to a
1071     * remote NAP or verify that a remote device connected to the local NAP.
1072     *
1073     * @param adapter The BT adapter.
1074     * @param device The remote device.
1075     * @param connect If the method should initiate the connection (is PANU)
1076     */
1077    private void connectPanOrIncomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device,
1078            boolean connect) {
1079        long start = -1;
1080        int mask, role;
1081        String methodName;
1082
1083        if (connect) {
1084            methodName = String.format("connectPan(device=%s)", device);
1085            mask = (ConnectProfileReceiver.STATE_CONNECTED_FLAG |
1086                    ConnectProfileReceiver.STATE_CONNECTING_FLAG);
1087            role = BluetoothPan.LOCAL_PANU_ROLE;
1088        } else {
1089            methodName = String.format("incomingPanConnection(device=%s)", device);
1090            mask = ConnectProfileReceiver.STATE_CONNECTED_FLAG;
1091            role = BluetoothPan.LOCAL_NAP_ROLE;
1092        }
1093
1094        if (!adapter.isEnabled()) {
1095            fail(String.format("%s bluetooth not enabled", methodName));
1096        }
1097
1098        if (!adapter.getBondedDevices().contains(device)) {
1099            fail(String.format("%s device not paired", methodName));
1100        }
1101
1102        mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN);
1103        assertNotNull(mPan);
1104        ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask);
1105
1106        int state = mPan.getConnectionState(device);
1107        switch (state) {
1108            case BluetoothPan.STATE_CONNECTED:
1109                removeReceiver(receiver);
1110                return;
1111            case BluetoothPan.STATE_CONNECTING:
1112                mask = 0; // Don't check for received intents since we might have missed them.
1113                break;
1114            case BluetoothPan.STATE_DISCONNECTED:
1115            case BluetoothPan.STATE_DISCONNECTING:
1116                start = System.currentTimeMillis();
1117                if (role == BluetoothPan.LOCAL_PANU_ROLE) {
1118                    Log.i("BT", "connect to pan");
1119                    assertTrue(mPan.connect(device));
1120                }
1121                break;
1122            default:
1123                removeReceiver(receiver);
1124                fail(String.format("%s invalid state: state=%d", methodName, state));
1125        }
1126
1127        long s = System.currentTimeMillis();
1128        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
1129            state = mPan.getConnectionState(device);
1130            if (state == BluetoothPan.STATE_CONNECTED
1131                    && (receiver.getFiredFlags() & mask) == mask) {
1132                long finish = receiver.getCompletedTime();
1133                if (start != -1 && finish != -1) {
1134                    writeOutput(String.format("%s completed in %d ms", methodName,
1135                            (finish - start)));
1136                } else {
1137                    writeOutput(String.format("%s completed", methodName));
1138                }
1139                removeReceiver(receiver);
1140                return;
1141            }
1142            sleep(POLL_TIME);
1143        }
1144
1145        int firedFlags = receiver.getFiredFlags();
1146        removeReceiver(receiver);
1147        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)",
1148                methodName, state, BluetoothPan.STATE_CONNECTED, firedFlags, mask));
1149    }
1150
1151    /**
1152     * Disconnects the PANU from a remote NAP and checks to make sure that the PANU is disconnected
1153     * and that the correct actions were broadcast.
1154     *
1155     * @param adapter The BT adapter.
1156     * @param device The remote device.
1157     */
1158    public void disconnectPan(BluetoothAdapter adapter, BluetoothDevice device) {
1159        disconnectFromRemoteOrVerifyConnectNap(adapter, device, true);
1160    }
1161
1162    /**
1163     * Checks that a remote PANU disconnects from the local NAP correctly and that the correct
1164     * actions were broadcast.
1165     *
1166     * @param adapter The BT adapter.
1167     * @param device The remote device.
1168     */
1169    public void incomingPanDisconnection(BluetoothAdapter adapter, BluetoothDevice device) {
1170        disconnectFromRemoteOrVerifyConnectNap(adapter, device, false);
1171    }
1172
1173    /**
1174     * Helper method used by {@link #disconnectPan(BluetoothAdapter, BluetoothDevice)} and
1175     * {@link #incomingPanDisconnection(BluetoothAdapter, BluetoothDevice)} to either disconnect
1176     * from a remote NAP or verify that a remote device disconnected from the local NAP.
1177     *
1178     * @param adapter The BT adapter.
1179     * @param device The remote device.
1180     * @param disconnect Whether the method should connect or verify.
1181     */
1182    private void disconnectFromRemoteOrVerifyConnectNap(BluetoothAdapter adapter,
1183            BluetoothDevice device, boolean disconnect) {
1184        long start = -1;
1185        int mask, role;
1186        String methodName;
1187
1188        if (disconnect) {
1189            methodName = String.format("disconnectPan(device=%s)", device);
1190            mask = (ConnectProfileReceiver.STATE_DISCONNECTED_FLAG |
1191                    ConnectProfileReceiver.STATE_DISCONNECTING_FLAG);
1192            role = BluetoothPan.LOCAL_PANU_ROLE;
1193        } else {
1194            methodName = String.format("incomingPanDisconnection(device=%s)", device);
1195            mask = ConnectProfileReceiver.STATE_DISCONNECTED_FLAG;
1196            role = BluetoothPan.LOCAL_NAP_ROLE;
1197        }
1198
1199        if (!adapter.isEnabled()) {
1200            fail(String.format("%s bluetooth not enabled", methodName));
1201        }
1202
1203        if (!adapter.getBondedDevices().contains(device)) {
1204            fail(String.format("%s device not paired", methodName));
1205        }
1206
1207        mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN);
1208        assertNotNull(mPan);
1209        ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask);
1210
1211        int state = mPan.getConnectionState(device);
1212        switch (state) {
1213            case BluetoothPan.STATE_CONNECTED:
1214            case BluetoothPan.STATE_CONNECTING:
1215                start = System.currentTimeMillis();
1216                if (role == BluetoothPan.LOCAL_PANU_ROLE) {
1217                    assertTrue(mPan.disconnect(device));
1218                }
1219                break;
1220            case BluetoothPan.STATE_DISCONNECTED:
1221                removeReceiver(receiver);
1222                return;
1223            case BluetoothPan.STATE_DISCONNECTING:
1224                mask = 0; // Don't check for received intents since we might have missed them.
1225                break;
1226            default:
1227                removeReceiver(receiver);
1228                fail(String.format("%s invalid state: state=%d", methodName, state));
1229        }
1230
1231        long s = System.currentTimeMillis();
1232        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
1233            state = mPan.getConnectionState(device);
1234            if (state == BluetoothInputDevice.STATE_DISCONNECTED
1235                    && (receiver.getFiredFlags() & mask) == mask) {
1236                long finish = receiver.getCompletedTime();
1237                if (start != -1 && finish != -1) {
1238                    writeOutput(String.format("%s completed in %d ms", methodName,
1239                            (finish - start)));
1240                } else {
1241                    writeOutput(String.format("%s completed", methodName));
1242                }
1243                removeReceiver(receiver);
1244                return;
1245            }
1246            sleep(POLL_TIME);
1247        }
1248
1249        int firedFlags = receiver.getFiredFlags();
1250        removeReceiver(receiver);
1251        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)",
1252                methodName, state, BluetoothInputDevice.STATE_DISCONNECTED, firedFlags, mask));
1253    }
1254
1255    /**
1256     * Opens a SCO channel using {@link android.media.AudioManager#startBluetoothSco()} and checks
1257     * to make sure that the channel is opened and that the correct actions were broadcast.
1258     *
1259     * @param adapter The BT adapter.
1260     * @param device The remote device.
1261     */
1262    public void startSco(BluetoothAdapter adapter, BluetoothDevice device) {
1263        startStopSco(adapter, device, true);
1264    }
1265
1266    /**
1267     * Closes a SCO channel using {@link android.media.AudioManager#stopBluetoothSco()} and checks
1268     *  to make sure that the channel is closed and that the correct actions were broadcast.
1269     *
1270     * @param adapter The BT adapter.
1271     * @param device The remote device.
1272     */
1273    public void stopSco(BluetoothAdapter adapter, BluetoothDevice device) {
1274        startStopSco(adapter, device, false);
1275    }
1276    /**
1277     * Helper method for {@link #startSco(BluetoothAdapter, BluetoothDevice)} and
1278     * {@link #stopSco(BluetoothAdapter, BluetoothDevice)}.
1279     *
1280     * @param adapter The BT adapter.
1281     * @param device The remote device.
1282     * @param isStart Whether the SCO channel should be opened.
1283     */
1284    private void startStopSco(BluetoothAdapter adapter, BluetoothDevice device, boolean isStart) {
1285        long start = -1;
1286        int mask;
1287        String methodName;
1288
1289        if (isStart) {
1290            methodName = String.format("startSco(device=%s)", device);
1291            mask = StartStopScoReceiver.STATE_CONNECTED_FLAG;
1292        } else {
1293            methodName = String.format("stopSco(device=%s)", device);
1294            mask = StartStopScoReceiver.STATE_DISCONNECTED_FLAG;
1295        }
1296
1297        if (!adapter.isEnabled()) {
1298            fail(String.format("%s bluetooth not enabled", methodName));
1299        }
1300
1301        if (!adapter.getBondedDevices().contains(device)) {
1302            fail(String.format("%s device not paired", methodName));
1303        }
1304
1305        AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
1306        assertNotNull(manager);
1307
1308        if (!manager.isBluetoothScoAvailableOffCall()) {
1309            fail(String.format("%s device does not support SCO", methodName));
1310        }
1311
1312        boolean isScoOn = manager.isBluetoothScoOn();
1313        if (isStart == isScoOn) {
1314            return;
1315        }
1316
1317        StartStopScoReceiver receiver = getStartStopScoReceiver(mask);
1318        start = System.currentTimeMillis();
1319        if (isStart) {
1320            manager.startBluetoothSco();
1321        } else {
1322            manager.stopBluetoothSco();
1323        }
1324
1325        long s = System.currentTimeMillis();
1326        while (System.currentTimeMillis() - s < START_STOP_SCO_TIMEOUT) {
1327            isScoOn = manager.isBluetoothScoOn();
1328            if (isStart == isScoOn && (receiver.getFiredFlags() & mask) == mask) {
1329                long finish = receiver.getCompletedTime();
1330                if (start != -1 && finish != -1) {
1331                    writeOutput(String.format("%s completed in %d ms", methodName,
1332                            (finish - start)));
1333                } else {
1334                    writeOutput(String.format("%s completed", methodName));
1335                }
1336                removeReceiver(receiver);
1337                return;
1338            }
1339            sleep(POLL_TIME);
1340        }
1341
1342        int firedFlags = receiver.getFiredFlags();
1343        removeReceiver(receiver);
1344        fail(String.format("%s timeout: on=%b (expected %b), flags=0x%x (expected 0x%x)",
1345                methodName, isScoOn, isStart, firedFlags, mask));
1346    }
1347
1348    /**
1349     * Writes a string to the logcat and a file if a file has been specified in the constructor.
1350     *
1351     * @param s The string to be written.
1352     */
1353    public void writeOutput(String s) {
1354        Log.i(mTag, s);
1355        if (mOutputWriter == null) {
1356            return;
1357        }
1358        try {
1359            mOutputWriter.write(s + "\n");
1360            mOutputWriter.flush();
1361        } catch (IOException e) {
1362            Log.w(mTag, "Could not write to output file", e);
1363        }
1364    }
1365
1366    private void addReceiver(BroadcastReceiver receiver, String[] actions) {
1367        IntentFilter filter = new IntentFilter();
1368        for (String action: actions) {
1369            filter.addAction(action);
1370        }
1371        mContext.registerReceiver(receiver, filter);
1372        mReceivers.add(receiver);
1373    }
1374
1375    private BluetoothReceiver getBluetoothReceiver(int expectedFlags) {
1376        String[] actions = {
1377                BluetoothAdapter.ACTION_DISCOVERY_FINISHED,
1378                BluetoothAdapter.ACTION_DISCOVERY_STARTED,
1379                BluetoothAdapter.ACTION_SCAN_MODE_CHANGED,
1380                BluetoothAdapter.ACTION_STATE_CHANGED};
1381        BluetoothReceiver receiver = new BluetoothReceiver(expectedFlags);
1382        addReceiver(receiver, actions);
1383        return receiver;
1384    }
1385
1386    private PairReceiver getPairReceiver(BluetoothDevice device, int passkey, byte[] pin,
1387            int expectedFlags) {
1388        String[] actions = {
1389                BluetoothDevice.ACTION_PAIRING_REQUEST,
1390                BluetoothDevice.ACTION_BOND_STATE_CHANGED};
1391        PairReceiver receiver = new PairReceiver(device, passkey, pin, expectedFlags);
1392        addReceiver(receiver, actions);
1393        return receiver;
1394    }
1395
1396    private ConnectProfileReceiver getConnectProfileReceiver(BluetoothDevice device, int profile,
1397            int expectedFlags) {
1398        String[] actions = {
1399                BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED,
1400                BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED,
1401                BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED};
1402        ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile,
1403                expectedFlags);
1404        addReceiver(receiver, actions);
1405        return receiver;
1406    }
1407
1408    private ConnectPanReceiver getConnectPanReceiver(BluetoothDevice device, int role,
1409            int expectedFlags) {
1410        String[] actions = {BluetoothPan.ACTION_CONNECTION_STATE_CHANGED};
1411        ConnectPanReceiver receiver = new ConnectPanReceiver(device, role, expectedFlags);
1412        addReceiver(receiver, actions);
1413        return receiver;
1414    }
1415
1416    private StartStopScoReceiver getStartStopScoReceiver(int expectedFlags) {
1417        String[] actions = {AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED};
1418        StartStopScoReceiver receiver = new StartStopScoReceiver(expectedFlags);
1419        addReceiver(receiver, actions);
1420        return receiver;
1421    }
1422
1423    private void removeReceiver(BroadcastReceiver receiver) {
1424        mContext.unregisterReceiver(receiver);
1425        mReceivers.remove(receiver);
1426    }
1427
1428    private BluetoothProfile connectProxy(BluetoothAdapter adapter, int profile) {
1429        switch (profile) {
1430            case BluetoothProfile.A2DP:
1431                if (mA2dp != null) {
1432                    return mA2dp;
1433                }
1434                break;
1435            case BluetoothProfile.HEADSET:
1436                if (mHeadset != null) {
1437                    return mHeadset;
1438                }
1439                break;
1440            case BluetoothProfile.INPUT_DEVICE:
1441                if (mInput != null) {
1442                    return mInput;
1443                }
1444                break;
1445            case BluetoothProfile.PAN:
1446                if (mPan != null) {
1447                    return mPan;
1448                }
1449                break;
1450            default:
1451                return null;
1452        }
1453        adapter.getProfileProxy(mContext, mServiceListener, profile);
1454        long s = System.currentTimeMillis();
1455        switch (profile) {
1456            case BluetoothProfile.A2DP:
1457                while (mA2dp == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1458                    sleep(POLL_TIME);
1459                }
1460                return mA2dp;
1461            case BluetoothProfile.HEADSET:
1462                while (mHeadset == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1463                    sleep(POLL_TIME);
1464                }
1465                return mHeadset;
1466            case BluetoothProfile.INPUT_DEVICE:
1467                while (mInput == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1468                    sleep(POLL_TIME);
1469                }
1470                return mInput;
1471            case BluetoothProfile.PAN:
1472                while (mPan == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1473                    sleep(POLL_TIME);
1474                }
1475                return mPan;
1476            default:
1477                return null;
1478        }
1479    }
1480
1481    private void sleep(long time) {
1482        try {
1483            Thread.sleep(time);
1484        } catch (InterruptedException e) {
1485        }
1486    }
1487}
1488