BluetoothTestUtils.java revision fca8e9d919443449807f87203609e53a64368f6b
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_UPDATED.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        assertTrue(adapter.cancelDiscovery());
682
683        while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) {
684            if (!adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) {
685                writeOutput(String.format("stopScan() completed in %d ms",
686                        (receiver.getCompletedTime() - start)));
687                removeReceiver(receiver);
688                return;
689            }
690            sleep(POLL_TIME);
691        }
692
693        int firedFlags = receiver.getFiredFlags();
694        removeReceiver(receiver);
695        fail(String.format("stopScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)",
696                adapter.isDiscovering(), firedFlags, mask));
697
698    }
699
700    /**
701     * Enables PAN tethering on the local device and checks to make sure that tethering is enabled.
702     *
703     * @param adapter The BT adapter.
704     */
705    public void enablePan(BluetoothAdapter adapter) {
706        if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN);
707        assertNotNull(mPan);
708
709        long start = System.currentTimeMillis();
710        mPan.setBluetoothTethering(true);
711        long stop = System.currentTimeMillis();
712        assertTrue(mPan.isTetheringOn());
713
714        writeOutput(String.format("enablePan() completed in %d ms", (stop - start)));
715    }
716
717    /**
718     * Disables PAN tethering on the local device and checks to make sure that tethering is
719     * disabled.
720     *
721     * @param adapter The BT adapter.
722     */
723    public void disablePan(BluetoothAdapter adapter) {
724        if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN);
725        assertNotNull(mPan);
726
727        long start = System.currentTimeMillis();
728        mPan.setBluetoothTethering(false);
729        long stop = System.currentTimeMillis();
730        assertFalse(mPan.isTetheringOn());
731
732        writeOutput(String.format("disablePan() completed in %d ms", (stop - start)));
733    }
734
735    /**
736     * Initiates a pairing with a remote device and checks to make sure that the devices are paired
737     * and that the correct actions were broadcast.
738     *
739     * @param adapter The BT adapter.
740     * @param device The remote device.
741     * @param passkey The pairing passkey if pairing requires a passkey. Any value if not.
742     * @param pin The pairing pin if pairing requires a pin. Any value if not.
743     */
744    public void pair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, byte[] pin) {
745        pairOrAcceptPair(adapter, device, passkey, pin, true);
746    }
747
748    /**
749     * Accepts a pairing with a remote device and checks to make sure that the devices are paired
750     * and that the correct actions were broadcast.
751     *
752     * @param adapter The BT adapter.
753     * @param device The remote device.
754     * @param passkey The pairing passkey if pairing requires a passkey. Any value if not.
755     * @param pin The pairing pin if pairing requires a pin. Any value if not.
756     */
757    public void acceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey,
758            byte[] pin) {
759        pairOrAcceptPair(adapter, device, passkey, pin, false);
760    }
761
762    /**
763     * Helper method used by {@link #pair(BluetoothAdapter, BluetoothDevice, int, byte[])} and
764     * {@link #acceptPair(BluetoothAdapter, BluetoothDevice, int, byte[])} to either pair or accept
765     * a pairing request.
766     *
767     * @param adapter The BT adapter.
768     * @param device The remote device.
769     * @param passkey The pairing passkey if pairing requires a passkey. Any value if not.
770     * @param pin The pairing pin if pairing requires a pin. Any value if not.
771     * @param shouldPair Whether to pair or accept the pair.
772     */
773    private void pairOrAcceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey,
774            byte[] pin, boolean shouldPair) {
775        int mask = PairReceiver.STATE_BONDING_FLAG | PairReceiver.STATE_BONDED_FLAG;
776        long start = -1;
777        String methodName;
778        if (shouldPair) {
779            methodName = String.format("pair(device=%s)", device);
780        } else {
781            methodName = String.format("acceptPair(device=%s)", device);
782        }
783
784        if (!adapter.isEnabled()) {
785            fail(String.format("%s bluetooth not enabled", methodName));
786        }
787
788        PairReceiver receiver = getPairReceiver(device, passkey, pin, mask);
789
790        int state = device.getBondState();
791        switch (state) {
792            case BluetoothDevice.BOND_NONE:
793                assertFalse(adapter.getBondedDevices().contains(device));
794                start = System.currentTimeMillis();
795                if (shouldPair) {
796                    assertTrue(device.createBond());
797                }
798                break;
799            case BluetoothDevice.BOND_BONDING:
800                mask = 0; // Don't check for received intents since we might have missed them.
801                break;
802            case BluetoothDevice.BOND_BONDED:
803                assertTrue(adapter.getBondedDevices().contains(device));
804                return;
805            default:
806                removeReceiver(receiver);
807                fail(String.format("%s invalid state: state=%d", methodName, state));
808        }
809
810        long s = System.currentTimeMillis();
811        while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) {
812            state = device.getBondState();
813            if (state == BluetoothDevice.BOND_BONDED && (receiver.getFiredFlags() & mask) == mask) {
814                assertTrue(adapter.getBondedDevices().contains(device));
815                long finish = receiver.getCompletedTime();
816                if (start != -1 && finish != -1) {
817                    writeOutput(String.format("%s completed in %d ms", methodName,
818                            (finish - start)));
819                } else {
820                    writeOutput(String.format("%s completed", methodName));
821                }
822                removeReceiver(receiver);
823                return;
824            }
825            sleep(POLL_TIME);
826        }
827
828        int firedFlags = receiver.getFiredFlags();
829        removeReceiver(receiver);
830        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
831                methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask));
832    }
833
834    /**
835     * Deletes a pairing with a remote device and checks to make sure that the devices are unpaired
836     * and that the correct actions were broadcast.
837     *
838     * @param adapter The BT adapter.
839     * @param device The remote device.
840     */
841    public void unpair(BluetoothAdapter adapter, BluetoothDevice device) {
842        int mask = PairReceiver.STATE_NONE_FLAG;
843        long start = -1;
844        String methodName = String.format("unpair(device=%s)", device);
845
846        if (!adapter.isEnabled()) {
847            fail(String.format("%s bluetooth not enabled", methodName));
848        }
849
850        PairReceiver receiver = getPairReceiver(device, 0, null, mask);
851
852        int state = device.getBondState();
853        switch (state) {
854            case BluetoothDevice.BOND_NONE:
855                assertFalse(adapter.getBondedDevices().contains(device));
856                removeReceiver(receiver);
857                return;
858            case BluetoothDevice.BOND_BONDING:
859                start = System.currentTimeMillis();
860                assertTrue(device.removeBond());
861                break;
862            case BluetoothDevice.BOND_BONDED:
863                assertTrue(adapter.getBondedDevices().contains(device));
864                start = System.currentTimeMillis();
865                assertTrue(device.removeBond());
866                break;
867            default:
868                removeReceiver(receiver);
869                fail(String.format("%s invalid state: state=%d", methodName, state));
870        }
871
872        long s = System.currentTimeMillis();
873        while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) {
874            if (device.getBondState() == BluetoothDevice.BOND_NONE
875                    && (receiver.getFiredFlags() & mask) == mask) {
876                assertFalse(adapter.getBondedDevices().contains(device));
877                long finish = receiver.getCompletedTime();
878                if (start != -1 && finish != -1) {
879                    writeOutput(String.format("%s completed in %d ms", methodName,
880                            (finish - start)));
881                } else {
882                    writeOutput(String.format("%s completed", methodName));
883                }
884                removeReceiver(receiver);
885                return;
886            }
887        }
888
889        int firedFlags = receiver.getFiredFlags();
890        removeReceiver(receiver);
891        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
892                methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask));
893    }
894
895    /**
896     * Connects a profile from the local device to a remote device and checks to make sure that the
897     * profile is connected and that the correct actions were broadcast.
898     *
899     * @param adapter The BT adapter.
900     * @param device The remote device.
901     * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP},
902     * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}.
903     * @param methodName The method name to printed in the logs.  If null, will be
904     * "connectProfile(profile=&lt;profile&gt;, device=&lt;device&gt;)"
905     */
906    public void connectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile,
907            String methodName) {
908        if (methodName == null) {
909            methodName = String.format("connectProfile(profile=%d, device=%s)", profile, device);
910        }
911        int mask = (ConnectProfileReceiver.STATE_CONNECTING_FLAG
912                | ConnectProfileReceiver.STATE_CONNECTED_FLAG);
913        long start = -1;
914
915        if (!adapter.isEnabled()) {
916            fail(String.format("%s bluetooth not enabled", methodName));
917        }
918
919        if (!adapter.getBondedDevices().contains(device)) {
920            fail(String.format("%s device not paired", methodName));
921        }
922
923        BluetoothProfile proxy = connectProxy(adapter, profile);
924        assertNotNull(proxy);
925
926        ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask);
927
928        int state = proxy.getConnectionState(device);
929        switch (state) {
930            case BluetoothProfile.STATE_CONNECTED:
931                removeReceiver(receiver);
932                return;
933            case BluetoothProfile.STATE_CONNECTING:
934                mask = 0; // Don't check for received intents since we might have missed them.
935                break;
936            case BluetoothProfile.STATE_DISCONNECTED:
937            case BluetoothProfile.STATE_DISCONNECTING:
938                start = System.currentTimeMillis();
939                if (profile == BluetoothProfile.A2DP) {
940                    assertTrue(((BluetoothA2dp)proxy).connect(device));
941                } else if (profile == BluetoothProfile.HEADSET) {
942                    assertTrue(((BluetoothHeadset)proxy).connect(device));
943                } else if (profile == BluetoothProfile.INPUT_DEVICE) {
944                    assertTrue(((BluetoothInputDevice)proxy).connect(device));
945                }
946                break;
947            default:
948                removeReceiver(receiver);
949                fail(String.format("%s invalid state: state=%d", methodName, state));
950        }
951
952        long s = System.currentTimeMillis();
953        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
954            state = proxy.getConnectionState(device);
955            if (state == BluetoothProfile.STATE_CONNECTED
956                    && (receiver.getFiredFlags() & mask) == mask) {
957                long finish = receiver.getCompletedTime();
958                if (start != -1 && finish != -1) {
959                    writeOutput(String.format("%s completed in %d ms", methodName,
960                            (finish - start)));
961                } else {
962                    writeOutput(String.format("%s completed", methodName));
963                }
964                removeReceiver(receiver);
965                return;
966            }
967            sleep(POLL_TIME);
968        }
969
970        int firedFlags = receiver.getFiredFlags();
971        removeReceiver(receiver);
972        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
973                methodName, state, BluetoothProfile.STATE_CONNECTED, firedFlags, mask));
974    }
975
976    /**
977     * Disconnects a profile between the local device and a remote device and checks to make sure
978     * that the profile is disconnected and that the correct actions were broadcast.
979     *
980     * @param adapter The BT adapter.
981     * @param device The remote device.
982     * @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP},
983     * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}.
984     * @param methodName The method name to printed in the logs.  If null, will be
985     * "connectProfile(profile=&lt;profile&gt;, device=&lt;device&gt;)"
986     */
987    public void disconnectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile,
988            String methodName) {
989        if (methodName == null) {
990            methodName = String.format("disconnectProfile(profile=%d, device=%s)", profile, device);
991        }
992        int mask = (ConnectProfileReceiver.STATE_DISCONNECTING_FLAG
993                | ConnectProfileReceiver.STATE_DISCONNECTED_FLAG);
994        long start = -1;
995
996        if (!adapter.isEnabled()) {
997            fail(String.format("%s bluetooth not enabled", methodName));
998        }
999
1000        if (!adapter.getBondedDevices().contains(device)) {
1001            fail(String.format("%s device not paired", methodName));
1002        }
1003
1004        BluetoothProfile proxy = connectProxy(adapter, profile);
1005        assertNotNull(proxy);
1006
1007        ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask);
1008
1009        int state = proxy.getConnectionState(device);
1010        switch (state) {
1011            case BluetoothProfile.STATE_CONNECTED:
1012            case BluetoothProfile.STATE_CONNECTING:
1013                start = System.currentTimeMillis();
1014                if (profile == BluetoothProfile.A2DP) {
1015                    assertTrue(((BluetoothA2dp)proxy).disconnect(device));
1016                } else if (profile == BluetoothProfile.HEADSET) {
1017                    assertTrue(((BluetoothHeadset)proxy).disconnect(device));
1018                } else if (profile == BluetoothProfile.INPUT_DEVICE) {
1019                    assertTrue(((BluetoothInputDevice)proxy).disconnect(device));
1020                }
1021                break;
1022            case BluetoothProfile.STATE_DISCONNECTED:
1023                removeReceiver(receiver);
1024                return;
1025            case BluetoothProfile.STATE_DISCONNECTING:
1026                mask = 0; // Don't check for received intents since we might have missed them.
1027                break;
1028            default:
1029                removeReceiver(receiver);
1030                fail(String.format("%s invalid state: state=%d", methodName, state));
1031        }
1032
1033        long s = System.currentTimeMillis();
1034        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
1035            state = proxy.getConnectionState(device);
1036            if (state == BluetoothProfile.STATE_DISCONNECTED
1037                    && (receiver.getFiredFlags() & mask) == mask) {
1038                long finish = receiver.getCompletedTime();
1039                if (start != -1 && finish != -1) {
1040                    writeOutput(String.format("%s completed in %d ms", methodName,
1041                            (finish - start)));
1042                } else {
1043                    writeOutput(String.format("%s completed", methodName));
1044                }
1045                removeReceiver(receiver);
1046                return;
1047            }
1048            sleep(POLL_TIME);
1049        }
1050
1051        int firedFlags = receiver.getFiredFlags();
1052        removeReceiver(receiver);
1053        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
1054                methodName, state, BluetoothProfile.STATE_DISCONNECTED, firedFlags, mask));
1055    }
1056
1057    /**
1058     * Connects the PANU to a remote NAP and checks to make sure that the PANU is connected and that
1059     * the correct actions were broadcast.
1060     *
1061     * @param adapter The BT adapter.
1062     * @param device The remote device.
1063     */
1064    public void connectPan(BluetoothAdapter adapter, BluetoothDevice device) {
1065        connectPanOrIncomingPanConnection(adapter, device, true);
1066    }
1067
1068    /**
1069     * Checks that a remote PANU connects to the local NAP correctly and that the correct actions
1070     * were broadcast.
1071     *
1072     * @param adapter The BT adapter.
1073     * @param device The remote device.
1074     */
1075    public void incomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device) {
1076        connectPanOrIncomingPanConnection(adapter, device, false);
1077    }
1078
1079    /**
1080     * Helper method used by {@link #connectPan(BluetoothAdapter, BluetoothDevice)} and
1081     * {@link #incomingPanConnection(BluetoothAdapter, BluetoothDevice)} to either connect to a
1082     * remote NAP or verify that a remote device connected to the local NAP.
1083     *
1084     * @param adapter The BT adapter.
1085     * @param device The remote device.
1086     * @param connect If the method should initiate the connection (is PANU)
1087     */
1088    private void connectPanOrIncomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device,
1089            boolean connect) {
1090        long start = -1;
1091        int mask, role;
1092        String methodName;
1093
1094        if (connect) {
1095            methodName = String.format("connectPan(device=%s)", device);
1096            mask = (ConnectProfileReceiver.STATE_CONNECTED_FLAG |
1097                    ConnectProfileReceiver.STATE_CONNECTING_FLAG);
1098            role = BluetoothPan.LOCAL_PANU_ROLE;
1099        } else {
1100            methodName = String.format("incomingPanConnection(device=%s)", device);
1101            mask = ConnectProfileReceiver.STATE_CONNECTED_FLAG;
1102            role = BluetoothPan.LOCAL_NAP_ROLE;
1103        }
1104
1105        if (!adapter.isEnabled()) {
1106            fail(String.format("%s bluetooth not enabled", methodName));
1107        }
1108
1109        if (!adapter.getBondedDevices().contains(device)) {
1110            fail(String.format("%s device not paired", methodName));
1111        }
1112
1113        mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN);
1114        assertNotNull(mPan);
1115        ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask);
1116
1117        int state = mPan.getConnectionState(device);
1118        switch (state) {
1119            case BluetoothPan.STATE_CONNECTED:
1120                removeReceiver(receiver);
1121                return;
1122            case BluetoothPan.STATE_CONNECTING:
1123                mask = 0; // Don't check for received intents since we might have missed them.
1124                break;
1125            case BluetoothPan.STATE_DISCONNECTED:
1126            case BluetoothPan.STATE_DISCONNECTING:
1127                start = System.currentTimeMillis();
1128                if (role == BluetoothPan.LOCAL_PANU_ROLE) {
1129                    Log.i("BT", "connect to pan");
1130                    assertTrue(mPan.connect(device));
1131                }
1132                break;
1133            default:
1134                removeReceiver(receiver);
1135                fail(String.format("%s invalid state: state=%d", methodName, state));
1136        }
1137
1138        long s = System.currentTimeMillis();
1139        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
1140            state = mPan.getConnectionState(device);
1141            if (state == BluetoothPan.STATE_CONNECTED
1142                    && (receiver.getFiredFlags() & mask) == mask) {
1143                long finish = receiver.getCompletedTime();
1144                if (start != -1 && finish != -1) {
1145                    writeOutput(String.format("%s completed in %d ms", methodName,
1146                            (finish - start)));
1147                } else {
1148                    writeOutput(String.format("%s completed", methodName));
1149                }
1150                removeReceiver(receiver);
1151                return;
1152            }
1153            sleep(POLL_TIME);
1154        }
1155
1156        int firedFlags = receiver.getFiredFlags();
1157        removeReceiver(receiver);
1158        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)",
1159                methodName, state, BluetoothPan.STATE_CONNECTED, firedFlags, mask));
1160    }
1161
1162    /**
1163     * Disconnects the PANU from a remote NAP and checks to make sure that the PANU is disconnected
1164     * and that the correct actions were broadcast.
1165     *
1166     * @param adapter The BT adapter.
1167     * @param device The remote device.
1168     */
1169    public void disconnectPan(BluetoothAdapter adapter, BluetoothDevice device) {
1170        disconnectFromRemoteOrVerifyConnectNap(adapter, device, true);
1171    }
1172
1173    /**
1174     * Checks that a remote PANU disconnects from the local NAP correctly and that the correct
1175     * actions were broadcast.
1176     *
1177     * @param adapter The BT adapter.
1178     * @param device The remote device.
1179     */
1180    public void incomingPanDisconnection(BluetoothAdapter adapter, BluetoothDevice device) {
1181        disconnectFromRemoteOrVerifyConnectNap(adapter, device, false);
1182    }
1183
1184    /**
1185     * Helper method used by {@link #disconnectPan(BluetoothAdapter, BluetoothDevice)} and
1186     * {@link #incomingPanDisconnection(BluetoothAdapter, BluetoothDevice)} to either disconnect
1187     * from a remote NAP or verify that a remote device disconnected from the local NAP.
1188     *
1189     * @param adapter The BT adapter.
1190     * @param device The remote device.
1191     * @param disconnect Whether the method should connect or verify.
1192     */
1193    private void disconnectFromRemoteOrVerifyConnectNap(BluetoothAdapter adapter,
1194            BluetoothDevice device, boolean disconnect) {
1195        long start = -1;
1196        int mask, role;
1197        String methodName;
1198
1199        if (disconnect) {
1200            methodName = String.format("disconnectPan(device=%s)", device);
1201            mask = (ConnectProfileReceiver.STATE_DISCONNECTED_FLAG |
1202                    ConnectProfileReceiver.STATE_DISCONNECTING_FLAG);
1203            role = BluetoothPan.LOCAL_PANU_ROLE;
1204        } else {
1205            methodName = String.format("incomingPanDisconnection(device=%s)", device);
1206            mask = ConnectProfileReceiver.STATE_DISCONNECTED_FLAG;
1207            role = BluetoothPan.LOCAL_NAP_ROLE;
1208        }
1209
1210        if (!adapter.isEnabled()) {
1211            fail(String.format("%s bluetooth not enabled", methodName));
1212        }
1213
1214        if (!adapter.getBondedDevices().contains(device)) {
1215            fail(String.format("%s device not paired", methodName));
1216        }
1217
1218        mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN);
1219        assertNotNull(mPan);
1220        ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask);
1221
1222        int state = mPan.getConnectionState(device);
1223        switch (state) {
1224            case BluetoothPan.STATE_CONNECTED:
1225            case BluetoothPan.STATE_CONNECTING:
1226                start = System.currentTimeMillis();
1227                if (role == BluetoothPan.LOCAL_PANU_ROLE) {
1228                    assertTrue(mPan.disconnect(device));
1229                }
1230                break;
1231            case BluetoothPan.STATE_DISCONNECTED:
1232                removeReceiver(receiver);
1233                return;
1234            case BluetoothPan.STATE_DISCONNECTING:
1235                mask = 0; // Don't check for received intents since we might have missed them.
1236                break;
1237            default:
1238                removeReceiver(receiver);
1239                fail(String.format("%s invalid state: state=%d", methodName, state));
1240        }
1241
1242        long s = System.currentTimeMillis();
1243        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
1244            state = mPan.getConnectionState(device);
1245            if (state == BluetoothInputDevice.STATE_DISCONNECTED
1246                    && (receiver.getFiredFlags() & mask) == mask) {
1247                long finish = receiver.getCompletedTime();
1248                if (start != -1 && finish != -1) {
1249                    writeOutput(String.format("%s completed in %d ms", methodName,
1250                            (finish - start)));
1251                } else {
1252                    writeOutput(String.format("%s completed", methodName));
1253                }
1254                removeReceiver(receiver);
1255                return;
1256            }
1257            sleep(POLL_TIME);
1258        }
1259
1260        int firedFlags = receiver.getFiredFlags();
1261        removeReceiver(receiver);
1262        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)",
1263                methodName, state, BluetoothInputDevice.STATE_DISCONNECTED, firedFlags, mask));
1264    }
1265
1266    /**
1267     * Opens a SCO channel using {@link android.media.AudioManager#startBluetoothSco()} and checks
1268     * to make sure that the channel is opened and that the correct actions were broadcast.
1269     *
1270     * @param adapter The BT adapter.
1271     * @param device The remote device.
1272     */
1273    public void startSco(BluetoothAdapter adapter, BluetoothDevice device) {
1274        startStopSco(adapter, device, true);
1275    }
1276
1277    /**
1278     * Closes a SCO channel using {@link android.media.AudioManager#stopBluetoothSco()} and checks
1279     *  to make sure that the channel is closed and that the correct actions were broadcast.
1280     *
1281     * @param adapter The BT adapter.
1282     * @param device The remote device.
1283     */
1284    public void stopSco(BluetoothAdapter adapter, BluetoothDevice device) {
1285        startStopSco(adapter, device, false);
1286    }
1287    /**
1288     * Helper method for {@link #startSco(BluetoothAdapter, BluetoothDevice)} and
1289     * {@link #stopSco(BluetoothAdapter, BluetoothDevice)}.
1290     *
1291     * @param adapter The BT adapter.
1292     * @param device The remote device.
1293     * @param isStart Whether the SCO channel should be opened.
1294     */
1295    private void startStopSco(BluetoothAdapter adapter, BluetoothDevice device, boolean isStart) {
1296        long start = -1;
1297        int mask;
1298        String methodName;
1299
1300        if (isStart) {
1301            methodName = String.format("startSco(device=%s)", device);
1302            mask = StartStopScoReceiver.STATE_CONNECTED_FLAG;
1303        } else {
1304            methodName = String.format("stopSco(device=%s)", device);
1305            mask = StartStopScoReceiver.STATE_DISCONNECTED_FLAG;
1306        }
1307
1308        if (!adapter.isEnabled()) {
1309            fail(String.format("%s bluetooth not enabled", methodName));
1310        }
1311
1312        if (!adapter.getBondedDevices().contains(device)) {
1313            fail(String.format("%s device not paired", methodName));
1314        }
1315
1316        AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
1317        assertNotNull(manager);
1318
1319        if (!manager.isBluetoothScoAvailableOffCall()) {
1320            fail(String.format("%s device does not support SCO", methodName));
1321        }
1322
1323        boolean isScoOn = manager.isBluetoothScoOn();
1324        if (isStart == isScoOn) {
1325            return;
1326        }
1327
1328        StartStopScoReceiver receiver = getStartStopScoReceiver(mask);
1329        start = System.currentTimeMillis();
1330        if (isStart) {
1331            manager.startBluetoothSco();
1332        } else {
1333            manager.stopBluetoothSco();
1334        }
1335
1336        long s = System.currentTimeMillis();
1337        while (System.currentTimeMillis() - s < START_STOP_SCO_TIMEOUT) {
1338            isScoOn = manager.isBluetoothScoOn();
1339            if (isStart == isScoOn && (receiver.getFiredFlags() & mask) == mask) {
1340                long finish = receiver.getCompletedTime();
1341                if (start != -1 && finish != -1) {
1342                    writeOutput(String.format("%s completed in %d ms", methodName,
1343                            (finish - start)));
1344                } else {
1345                    writeOutput(String.format("%s completed", methodName));
1346                }
1347                removeReceiver(receiver);
1348                return;
1349            }
1350            sleep(POLL_TIME);
1351        }
1352
1353        int firedFlags = receiver.getFiredFlags();
1354        removeReceiver(receiver);
1355        fail(String.format("%s timeout: on=%b (expected %b), flags=0x%x (expected 0x%x)",
1356                methodName, isScoOn, isStart, firedFlags, mask));
1357    }
1358
1359    /**
1360     * Writes a string to the logcat and a file if a file has been specified in the constructor.
1361     *
1362     * @param s The string to be written.
1363     */
1364    public void writeOutput(String s) {
1365        Log.i(mTag, s);
1366        if (mOutputWriter == null) {
1367            return;
1368        }
1369        try {
1370            mOutputWriter.write(s + "\n");
1371            mOutputWriter.flush();
1372        } catch (IOException e) {
1373            Log.w(mTag, "Could not write to output file", e);
1374        }
1375    }
1376
1377    private void addReceiver(BroadcastReceiver receiver, String[] actions) {
1378        IntentFilter filter = new IntentFilter();
1379        for (String action: actions) {
1380            filter.addAction(action);
1381        }
1382        mContext.registerReceiver(receiver, filter);
1383        mReceivers.add(receiver);
1384    }
1385
1386    private BluetoothReceiver getBluetoothReceiver(int expectedFlags) {
1387        String[] actions = {
1388                BluetoothAdapter.ACTION_DISCOVERY_FINISHED,
1389                BluetoothAdapter.ACTION_DISCOVERY_STARTED,
1390                BluetoothAdapter.ACTION_SCAN_MODE_CHANGED,
1391                BluetoothAdapter.ACTION_STATE_CHANGED};
1392        BluetoothReceiver receiver = new BluetoothReceiver(expectedFlags);
1393        addReceiver(receiver, actions);
1394        return receiver;
1395    }
1396
1397    private PairReceiver getPairReceiver(BluetoothDevice device, int passkey, byte[] pin,
1398            int expectedFlags) {
1399        String[] actions = {
1400                BluetoothDevice.ACTION_PAIRING_REQUEST,
1401                BluetoothDevice.ACTION_BOND_STATE_CHANGED};
1402        PairReceiver receiver = new PairReceiver(device, passkey, pin, expectedFlags);
1403        addReceiver(receiver, actions);
1404        return receiver;
1405    }
1406
1407    private ConnectProfileReceiver getConnectProfileReceiver(BluetoothDevice device, int profile,
1408            int expectedFlags) {
1409        String[] actions = {
1410                BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED,
1411                BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED,
1412                BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED};
1413        ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile,
1414                expectedFlags);
1415        addReceiver(receiver, actions);
1416        return receiver;
1417    }
1418
1419    private ConnectPanReceiver getConnectPanReceiver(BluetoothDevice device, int role,
1420            int expectedFlags) {
1421        String[] actions = {BluetoothPan.ACTION_CONNECTION_STATE_CHANGED};
1422        ConnectPanReceiver receiver = new ConnectPanReceiver(device, role, expectedFlags);
1423        addReceiver(receiver, actions);
1424        return receiver;
1425    }
1426
1427    private StartStopScoReceiver getStartStopScoReceiver(int expectedFlags) {
1428        String[] actions = {AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED};
1429        StartStopScoReceiver receiver = new StartStopScoReceiver(expectedFlags);
1430        addReceiver(receiver, actions);
1431        return receiver;
1432    }
1433
1434    private void removeReceiver(BroadcastReceiver receiver) {
1435        mContext.unregisterReceiver(receiver);
1436        mReceivers.remove(receiver);
1437    }
1438
1439    private BluetoothProfile connectProxy(BluetoothAdapter adapter, int profile) {
1440        switch (profile) {
1441            case BluetoothProfile.A2DP:
1442                if (mA2dp != null) {
1443                    return mA2dp;
1444                }
1445                break;
1446            case BluetoothProfile.HEADSET:
1447                if (mHeadset != null) {
1448                    return mHeadset;
1449                }
1450                break;
1451            case BluetoothProfile.INPUT_DEVICE:
1452                if (mInput != null) {
1453                    return mInput;
1454                }
1455                break;
1456            case BluetoothProfile.PAN:
1457                if (mPan != null) {
1458                    return mPan;
1459                }
1460                break;
1461            default:
1462                return null;
1463        }
1464        adapter.getProfileProxy(mContext, mServiceListener, profile);
1465        long s = System.currentTimeMillis();
1466        switch (profile) {
1467            case BluetoothProfile.A2DP:
1468                while (mA2dp == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1469                    sleep(POLL_TIME);
1470                }
1471                return mA2dp;
1472            case BluetoothProfile.HEADSET:
1473                while (mHeadset == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1474                    sleep(POLL_TIME);
1475                }
1476                return mHeadset;
1477            case BluetoothProfile.INPUT_DEVICE:
1478                while (mInput == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1479                    sleep(POLL_TIME);
1480                }
1481                return mInput;
1482            case BluetoothProfile.PAN:
1483                while (mPan == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1484                    sleep(POLL_TIME);
1485                }
1486                return mPan;
1487            default:
1488                return null;
1489        }
1490    }
1491
1492    private void sleep(long time) {
1493        try {
1494            Thread.sleep(time);
1495        } catch (InterruptedException e) {
1496        }
1497    }
1498}
1499