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