BluetoothTestUtils.java revision 6570340f713d0c716ce4860f6f43d1efa230242a
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 = System.currentTimeMillis();
429        BluetoothReceiver receiver = getBluetoothReceiver(mask);
430
431        writeOutput("Enabling Bluetooth adapter.");
432        assertFalse(adapter.isEnabled());
433        assertTrue(adapter.enable());
434
435        int state = BluetoothAdapter.STATE_OFF;
436        long s = System.currentTimeMillis();
437        while (System.currentTimeMillis() - s < ENABLE_DISABLE_TIMEOUT) {
438            state = adapter.getState();
439            if (state == BluetoothAdapter.STATE_ON
440                    && (receiver.getFiredFlags() & mask) == mask) {
441                assertTrue(adapter.isEnabled());
442                long finish = receiver.getCompletedTime();
443                if (start != -1 && finish != -1) {
444                    writeOutput(String.format("enable() completed in %d ms", (finish - start)));
445                } else {
446                    writeOutput("enable() completed");
447                }
448                removeReceiver(receiver);
449                return;
450            }
451            sleep(POLL_TIME);
452        }
453
454        int firedFlags = receiver.getFiredFlags();
455        removeReceiver(receiver);
456        fail(String.format("enable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
457                state, BluetoothAdapter.STATE_ON, firedFlags, mask));
458    }
459
460    /**
461     * Disables Bluetooth and checks to make sure that Bluetooth was turned off and that the correct
462     * actions were broadcast.
463     *
464     * @param adapter The BT adapter.
465     */
466    public void disable(BluetoothAdapter adapter) {
467        int mask = (BluetoothReceiver.STATE_TURNING_OFF_FLAG | BluetoothReceiver.STATE_OFF_FLAG
468                | BluetoothReceiver.SCAN_MODE_NONE_FLAG);
469        long start = System.currentTimeMillis();
470        BluetoothReceiver receiver = getBluetoothReceiver(mask);
471
472        writeOutput("Disabling Bluetooth adapter.");
473        assertTrue(adapter.isEnabled());
474        assertTrue(adapter.disable());
475
476        int state = BluetoothAdapter.STATE_OFF;
477        long s = System.currentTimeMillis();
478        while (System.currentTimeMillis() - s < ENABLE_DISABLE_TIMEOUT) {
479            state = adapter.getState();
480            if (state == BluetoothAdapter.STATE_OFF
481                    && (receiver.getFiredFlags() & mask) == mask) {
482                assertFalse(adapter.isEnabled());
483                long finish = receiver.getCompletedTime();
484                if (start != -1 && finish != -1) {
485                    writeOutput(String.format("disable() completed in %d ms", (finish - start)));
486                } else {
487                    writeOutput("disable() completed");
488                }
489                removeReceiver(receiver);
490                return;
491            }
492            sleep(POLL_TIME);
493        }
494
495        int firedFlags = receiver.getFiredFlags();
496        removeReceiver(receiver);
497        fail(String.format("disable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
498                state, BluetoothAdapter.STATE_OFF, firedFlags, mask));
499    }
500
501    /**
502     * Puts the local device into discoverable mode and checks to make sure that the local device
503     * is in discoverable mode and that the correct actions were broadcast.
504     *
505     * @param adapter The BT adapter.
506     */
507    public void discoverable(BluetoothAdapter adapter) {
508        int mask = BluetoothReceiver.SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG;
509
510        if (!adapter.isEnabled()) {
511            fail("discoverable() bluetooth not enabled");
512        }
513
514        int scanMode = adapter.getScanMode();
515        if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
516            return;
517        }
518
519        BluetoothReceiver receiver = getBluetoothReceiver(mask);
520
521        assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE, scanMode);
522        long start = System.currentTimeMillis();
523        assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE));
524
525        while (System.currentTimeMillis() - start < DISCOVERABLE_UNDISCOVERABLE_TIMEOUT) {
526            scanMode = adapter.getScanMode();
527            if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
528                    && (receiver.getFiredFlags() & mask) == mask) {
529                writeOutput(String.format("discoverable() completed in %d ms",
530                        (receiver.getCompletedTime() - start)));
531                removeReceiver(receiver);
532                return;
533            }
534            sleep(POLL_TIME);
535        }
536
537        int firedFlags = receiver.getFiredFlags();
538        removeReceiver(receiver);
539        fail(String.format("discoverable() timeout: scanMode=%d (expected %d), flags=0x%x "
540                + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,
541                firedFlags, mask));
542    }
543
544    /**
545     * Puts the local device into connectable only mode and checks to make sure that the local
546     * device is in in connectable mode and that the correct actions were broadcast.
547     *
548     * @param adapter The BT adapter.
549     */
550    public void undiscoverable(BluetoothAdapter adapter) {
551        int mask = BluetoothReceiver.SCAN_MODE_CONNECTABLE_FLAG;
552
553        if (!adapter.isEnabled()) {
554            fail("undiscoverable() bluetooth not enabled");
555        }
556
557        int scanMode = adapter.getScanMode();
558        if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) {
559            return;
560        }
561
562        BluetoothReceiver receiver = getBluetoothReceiver(mask);
563
564        assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, scanMode);
565        long start = System.currentTimeMillis();
566        assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE));
567
568        while (System.currentTimeMillis() - start < DISCOVERABLE_UNDISCOVERABLE_TIMEOUT) {
569            scanMode = adapter.getScanMode();
570            if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE
571                    && (receiver.getFiredFlags() & mask) == mask) {
572                writeOutput(String.format("undiscoverable() completed in %d ms",
573                        (receiver.getCompletedTime() - start)));
574                removeReceiver(receiver);
575                return;
576            }
577            sleep(POLL_TIME);
578        }
579
580        int firedFlags = receiver.getFiredFlags();
581        removeReceiver(receiver);
582        fail(String.format("undiscoverable() timeout: scanMode=%d (expected %d), flags=0x%x "
583                + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE, firedFlags,
584                mask));
585    }
586
587    /**
588     * Starts a scan for remote devices and checks to make sure that the local device is scanning
589     * and that the correct actions were broadcast.
590     *
591     * @param adapter The BT adapter.
592     */
593    public void startScan(BluetoothAdapter adapter) {
594        int mask = BluetoothReceiver.DISCOVERY_STARTED_FLAG;
595
596        if (!adapter.isEnabled()) {
597            fail("startScan() bluetooth not enabled");
598        }
599
600        if (adapter.isDiscovering()) {
601            return;
602        }
603
604        BluetoothReceiver receiver = getBluetoothReceiver(mask);
605
606        long start = System.currentTimeMillis();
607        assertTrue(adapter.startDiscovery());
608
609        while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) {
610            if (adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) {
611                writeOutput(String.format("startScan() completed in %d ms",
612                        (receiver.getCompletedTime() - start)));
613                removeReceiver(receiver);
614                return;
615            }
616            sleep(POLL_TIME);
617        }
618
619        int firedFlags = receiver.getFiredFlags();
620        removeReceiver(receiver);
621        fail(String.format("startScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)",
622                adapter.isDiscovering(), firedFlags, mask));
623    }
624
625    /**
626     * Stops a scan for remote devices and checks to make sure that the local device is not scanning
627     * and that the correct actions were broadcast.
628     *
629     * @param adapter The BT adapter.
630     */
631    public void stopScan(BluetoothAdapter adapter) {
632        int mask = BluetoothReceiver.DISCOVERY_FINISHED_FLAG;
633
634        if (!adapter.isEnabled()) {
635            fail("stopScan() 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.cancelDiscovery());
646
647        while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) {
648            if (!adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) {
649                writeOutput(String.format("stopScan() 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("stopScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)",
660                adapter.isDiscovering(), firedFlags, mask));
661
662    }
663
664    /**
665     * Enables PAN tethering on the local device and checks to make sure that tethering is enabled.
666     *
667     * @param adapter The BT adapter.
668     */
669    public void enablePan(BluetoothAdapter adapter) {
670        if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN);
671        assertNotNull(mPan);
672
673        long start = System.currentTimeMillis();
674        mPan.setBluetoothTethering(true);
675        long stop = System.currentTimeMillis();
676        assertTrue(mPan.isTetheringOn());
677
678        writeOutput(String.format("enablePan() completed in %d ms", (stop - start)));
679    }
680
681    /**
682     * Disables PAN tethering on the local device and checks to make sure that tethering is
683     * disabled.
684     *
685     * @param adapter The BT adapter.
686     */
687    public void disablePan(BluetoothAdapter adapter) {
688        if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN);
689        assertNotNull(mPan);
690
691        long start = System.currentTimeMillis();
692        mPan.setBluetoothTethering(false);
693        long stop = System.currentTimeMillis();
694        assertFalse(mPan.isTetheringOn());
695
696        writeOutput(String.format("disablePan() completed in %d ms", (stop - start)));
697    }
698
699    /**
700     * Initiates a pairing with a remote device and checks to make sure that the devices are paired
701     * and that the correct actions were broadcast.
702     *
703     * @param adapter The BT adapter.
704     * @param device The remote device.
705     * @param passkey The pairing passkey if pairing requires a passkey. Any value if not.
706     * @param pin The pairing pin if pairing requires a pin. Any value if not.
707     */
708    public void pair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, byte[] pin) {
709        pairOrAcceptPair(adapter, device, passkey, pin, true);
710    }
711
712    /**
713     * Accepts a pairing with a remote device and checks to make sure that the devices are paired
714     * and that the correct actions were broadcast.
715     *
716     * @param adapter The BT adapter.
717     * @param device The remote device.
718     * @param passkey The pairing passkey if pairing requires a passkey. Any value if not.
719     * @param pin The pairing pin if pairing requires a pin. Any value if not.
720     */
721    public void acceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey,
722            byte[] pin) {
723        pairOrAcceptPair(adapter, device, passkey, pin, false);
724    }
725
726    /**
727     * Helper method used by {@link #pair(BluetoothAdapter, BluetoothDevice, int, byte[])} and
728     * {@link #acceptPair(BluetoothAdapter, BluetoothDevice, int, byte[])} to either pair or accept
729     * a pairing request.
730     *
731     * @param adapter The BT adapter.
732     * @param device The remote device.
733     * @param passkey The pairing passkey if pairing requires a passkey. Any value if not.
734     * @param pin The pairing pin if pairing requires a pin. Any value if not.
735     * @param shouldPair Whether to pair or accept the pair.
736     */
737    private void pairOrAcceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey,
738            byte[] pin, boolean shouldPair) {
739        int mask = PairReceiver.STATE_BONDING_FLAG | PairReceiver.STATE_BONDED_FLAG;
740        long start = -1;
741        String methodName;
742        if (shouldPair) {
743            methodName = String.format("pair(device=%s)", device);
744        } else {
745            methodName = String.format("acceptPair(device=%s)", device);
746        }
747
748        if (!adapter.isEnabled()) {
749            fail(String.format("%s bluetooth not enabled", methodName));
750        }
751
752        PairReceiver receiver = getPairReceiver(device, passkey, pin, mask);
753
754        int state = device.getBondState();
755        switch (state) {
756            case BluetoothDevice.BOND_NONE:
757                assertFalse(adapter.getBondedDevices().contains(device));
758                start = System.currentTimeMillis();
759                if (shouldPair) {
760                    assertTrue(device.createBond());
761                }
762                break;
763            case BluetoothDevice.BOND_BONDING:
764                mask = 0; // Don't check for received intents since we might have missed them.
765                break;
766            case BluetoothDevice.BOND_BONDED:
767                assertTrue(adapter.getBondedDevices().contains(device));
768                return;
769            default:
770                removeReceiver(receiver);
771                fail(String.format("%s invalid state: state=%d", methodName, state));
772        }
773
774        long s = System.currentTimeMillis();
775        while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) {
776            state = device.getBondState();
777            if (state == BluetoothDevice.BOND_BONDED && (receiver.getFiredFlags() & mask) == mask) {
778                assertTrue(adapter.getBondedDevices().contains(device));
779                long finish = receiver.getCompletedTime();
780                if (start != -1 && finish != -1) {
781                    writeOutput(String.format("%s completed in %d ms", methodName,
782                            (finish - start)));
783                } else {
784                    writeOutput(String.format("%s completed", methodName));
785                }
786                removeReceiver(receiver);
787                return;
788            }
789            sleep(POLL_TIME);
790        }
791
792        int firedFlags = receiver.getFiredFlags();
793        removeReceiver(receiver);
794        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
795                methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask));
796    }
797
798    /**
799     * Deletes a pairing with a remote device and checks to make sure that the devices are unpaired
800     * and that the correct actions were broadcast.
801     *
802     * @param adapter The BT adapter.
803     * @param device The remote device.
804     */
805    public void unpair(BluetoothAdapter adapter, BluetoothDevice device) {
806        int mask = PairReceiver.STATE_NONE_FLAG;
807        long start = -1;
808        String methodName = String.format("unpair(device=%s)", device);
809
810        if (!adapter.isEnabled()) {
811            fail(String.format("%s bluetooth not enabled", methodName));
812        }
813
814        PairReceiver receiver = getPairReceiver(device, 0, null, mask);
815
816        int state = device.getBondState();
817        switch (state) {
818            case BluetoothDevice.BOND_NONE:
819                assertFalse(adapter.getBondedDevices().contains(device));
820                removeReceiver(receiver);
821                return;
822            case BluetoothDevice.BOND_BONDING:
823                start = System.currentTimeMillis();
824                assertTrue(device.removeBond());
825                break;
826            case BluetoothDevice.BOND_BONDED:
827                assertTrue(adapter.getBondedDevices().contains(device));
828                start = System.currentTimeMillis();
829                assertTrue(device.removeBond());
830                break;
831            default:
832                removeReceiver(receiver);
833                fail(String.format("%s invalid state: state=%d", methodName, state));
834        }
835
836        long s = System.currentTimeMillis();
837        while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) {
838            if (device.getBondState() == BluetoothDevice.BOND_NONE
839                    && (receiver.getFiredFlags() & mask) == mask) {
840                assertFalse(adapter.getBondedDevices().contains(device));
841                long finish = receiver.getCompletedTime();
842                if (start != -1 && finish != -1) {
843                    writeOutput(String.format("%s completed in %d ms", methodName,
844                            (finish - start)));
845                } else {
846                    writeOutput(String.format("%s completed", methodName));
847                }
848                removeReceiver(receiver);
849                return;
850            }
851        }
852
853        int firedFlags = receiver.getFiredFlags();
854        removeReceiver(receiver);
855        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
856                methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask));
857    }
858
859    /**
860     * Deletes all pairings of remote devices
861     * @param adapter the BT adapter
862     */
863    public void unpairAll(BluetoothAdapter adapter) {
864        Set<BluetoothDevice> devices = adapter.getBondedDevices();
865        for (BluetoothDevice device : devices) {
866            unpair(adapter, device);
867        }
868    }
869
870    /**
871     * Connects a profile from the local device to a remote device and checks to make sure that the
872     * profile is connected and that the correct actions were broadcast.
873     *
874     * @param adapter The BT adapter.
875     * @param device The remote device.
876     * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP},
877     * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}.
878     * @param methodName The method name to printed in the logs.  If null, will be
879     * "connectProfile(profile=&lt;profile&gt;, device=&lt;device&gt;)"
880     */
881    public void connectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile,
882            String methodName) {
883        if (methodName == null) {
884            methodName = String.format("connectProfile(profile=%d, device=%s)", profile, device);
885        }
886        int mask = (ConnectProfileReceiver.STATE_CONNECTING_FLAG
887                | ConnectProfileReceiver.STATE_CONNECTED_FLAG);
888        long start = -1;
889
890        if (!adapter.isEnabled()) {
891            fail(String.format("%s bluetooth not enabled", methodName));
892        }
893
894        if (!adapter.getBondedDevices().contains(device)) {
895            fail(String.format("%s device not paired", methodName));
896        }
897
898        BluetoothProfile proxy = connectProxy(adapter, profile);
899        assertNotNull(proxy);
900
901        ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask);
902
903        int state = proxy.getConnectionState(device);
904        switch (state) {
905            case BluetoothProfile.STATE_CONNECTED:
906                removeReceiver(receiver);
907                return;
908            case BluetoothProfile.STATE_CONNECTING:
909                mask = 0; // Don't check for received intents since we might have missed them.
910                break;
911            case BluetoothProfile.STATE_DISCONNECTED:
912            case BluetoothProfile.STATE_DISCONNECTING:
913                start = System.currentTimeMillis();
914                if (profile == BluetoothProfile.A2DP) {
915                    assertTrue(((BluetoothA2dp)proxy).connect(device));
916                } else if (profile == BluetoothProfile.HEADSET) {
917                    assertTrue(((BluetoothHeadset)proxy).connect(device));
918                } else if (profile == BluetoothProfile.INPUT_DEVICE) {
919                    assertTrue(((BluetoothInputDevice)proxy).connect(device));
920                }
921                break;
922            default:
923                removeReceiver(receiver);
924                fail(String.format("%s invalid state: state=%d", methodName, state));
925        }
926
927        long s = System.currentTimeMillis();
928        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
929            state = proxy.getConnectionState(device);
930            if (state == BluetoothProfile.STATE_CONNECTED
931                    && (receiver.getFiredFlags() & mask) == mask) {
932                long finish = receiver.getCompletedTime();
933                if (start != -1 && finish != -1) {
934                    writeOutput(String.format("%s completed in %d ms", methodName,
935                            (finish - start)));
936                } else {
937                    writeOutput(String.format("%s completed", methodName));
938                }
939                removeReceiver(receiver);
940                return;
941            }
942            sleep(POLL_TIME);
943        }
944
945        int firedFlags = receiver.getFiredFlags();
946        removeReceiver(receiver);
947        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
948                methodName, state, BluetoothProfile.STATE_CONNECTED, firedFlags, mask));
949    }
950
951    /**
952     * Disconnects a profile between the local device and a remote device and checks to make sure
953     * that the profile is disconnected and that the correct actions were broadcast.
954     *
955     * @param adapter The BT adapter.
956     * @param device The remote device.
957     * @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP},
958     * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}.
959     * @param methodName The method name to printed in the logs.  If null, will be
960     * "connectProfile(profile=&lt;profile&gt;, device=&lt;device&gt;)"
961     */
962    public void disconnectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile,
963            String methodName) {
964        if (methodName == null) {
965            methodName = String.format("disconnectProfile(profile=%d, device=%s)", profile, device);
966        }
967        int mask = (ConnectProfileReceiver.STATE_DISCONNECTING_FLAG
968                | ConnectProfileReceiver.STATE_DISCONNECTED_FLAG);
969        long start = -1;
970
971        if (!adapter.isEnabled()) {
972            fail(String.format("%s bluetooth not enabled", methodName));
973        }
974
975        if (!adapter.getBondedDevices().contains(device)) {
976            fail(String.format("%s device not paired", methodName));
977        }
978
979        BluetoothProfile proxy = connectProxy(adapter, profile);
980        assertNotNull(proxy);
981
982        ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask);
983
984        int state = proxy.getConnectionState(device);
985        switch (state) {
986            case BluetoothProfile.STATE_CONNECTED:
987            case BluetoothProfile.STATE_CONNECTING:
988                start = System.currentTimeMillis();
989                if (profile == BluetoothProfile.A2DP) {
990                    assertTrue(((BluetoothA2dp)proxy).disconnect(device));
991                } else if (profile == BluetoothProfile.HEADSET) {
992                    assertTrue(((BluetoothHeadset)proxy).disconnect(device));
993                } else if (profile == BluetoothProfile.INPUT_DEVICE) {
994                    assertTrue(((BluetoothInputDevice)proxy).disconnect(device));
995                }
996                break;
997            case BluetoothProfile.STATE_DISCONNECTED:
998                removeReceiver(receiver);
999                return;
1000            case BluetoothProfile.STATE_DISCONNECTING:
1001                mask = 0; // Don't check for received intents since we might have missed them.
1002                break;
1003            default:
1004                removeReceiver(receiver);
1005                fail(String.format("%s invalid state: state=%d", methodName, state));
1006        }
1007
1008        long s = System.currentTimeMillis();
1009        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
1010            state = proxy.getConnectionState(device);
1011            if (state == BluetoothProfile.STATE_DISCONNECTED
1012                    && (receiver.getFiredFlags() & mask) == mask) {
1013                long finish = receiver.getCompletedTime();
1014                if (start != -1 && finish != -1) {
1015                    writeOutput(String.format("%s completed in %d ms", methodName,
1016                            (finish - start)));
1017                } else {
1018                    writeOutput(String.format("%s completed", methodName));
1019                }
1020                removeReceiver(receiver);
1021                return;
1022            }
1023            sleep(POLL_TIME);
1024        }
1025
1026        int firedFlags = receiver.getFiredFlags();
1027        removeReceiver(receiver);
1028        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
1029                methodName, state, BluetoothProfile.STATE_DISCONNECTED, firedFlags, mask));
1030    }
1031
1032    /**
1033     * Connects the PANU to a remote NAP and checks to make sure that the PANU is connected and that
1034     * the correct actions were broadcast.
1035     *
1036     * @param adapter The BT adapter.
1037     * @param device The remote device.
1038     */
1039    public void connectPan(BluetoothAdapter adapter, BluetoothDevice device) {
1040        connectPanOrIncomingPanConnection(adapter, device, true);
1041    }
1042
1043    /**
1044     * Checks that a remote PANU connects to the local NAP correctly and that the correct actions
1045     * were broadcast.
1046     *
1047     * @param adapter The BT adapter.
1048     * @param device The remote device.
1049     */
1050    public void incomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device) {
1051        connectPanOrIncomingPanConnection(adapter, device, false);
1052    }
1053
1054    /**
1055     * Helper method used by {@link #connectPan(BluetoothAdapter, BluetoothDevice)} and
1056     * {@link #incomingPanConnection(BluetoothAdapter, BluetoothDevice)} to either connect to a
1057     * remote NAP or verify that a remote device connected to the local NAP.
1058     *
1059     * @param adapter The BT adapter.
1060     * @param device The remote device.
1061     * @param connect If the method should initiate the connection (is PANU)
1062     */
1063    private void connectPanOrIncomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device,
1064            boolean connect) {
1065        long start = -1;
1066        int mask, role;
1067        String methodName;
1068
1069        if (connect) {
1070            methodName = String.format("connectPan(device=%s)", device);
1071            mask = (ConnectProfileReceiver.STATE_CONNECTED_FLAG |
1072                    ConnectProfileReceiver.STATE_CONNECTING_FLAG);
1073            role = BluetoothPan.LOCAL_PANU_ROLE;
1074        } else {
1075            methodName = String.format("incomingPanConnection(device=%s)", device);
1076            mask = ConnectProfileReceiver.STATE_CONNECTED_FLAG;
1077            role = BluetoothPan.LOCAL_NAP_ROLE;
1078        }
1079
1080        if (!adapter.isEnabled()) {
1081            fail(String.format("%s bluetooth not enabled", methodName));
1082        }
1083
1084        if (!adapter.getBondedDevices().contains(device)) {
1085            fail(String.format("%s device not paired", methodName));
1086        }
1087
1088        mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN);
1089        assertNotNull(mPan);
1090        ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask);
1091
1092        int state = mPan.getConnectionState(device);
1093        switch (state) {
1094            case BluetoothPan.STATE_CONNECTED:
1095                removeReceiver(receiver);
1096                return;
1097            case BluetoothPan.STATE_CONNECTING:
1098                mask = 0; // Don't check for received intents since we might have missed them.
1099                break;
1100            case BluetoothPan.STATE_DISCONNECTED:
1101            case BluetoothPan.STATE_DISCONNECTING:
1102                start = System.currentTimeMillis();
1103                if (role == BluetoothPan.LOCAL_PANU_ROLE) {
1104                    Log.i("BT", "connect to pan");
1105                    assertTrue(mPan.connect(device));
1106                }
1107                break;
1108            default:
1109                removeReceiver(receiver);
1110                fail(String.format("%s invalid state: state=%d", methodName, state));
1111        }
1112
1113        long s = System.currentTimeMillis();
1114        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
1115            state = mPan.getConnectionState(device);
1116            if (state == BluetoothPan.STATE_CONNECTED
1117                    && (receiver.getFiredFlags() & mask) == mask) {
1118                long finish = receiver.getCompletedTime();
1119                if (start != -1 && finish != -1) {
1120                    writeOutput(String.format("%s completed in %d ms", methodName,
1121                            (finish - start)));
1122                } else {
1123                    writeOutput(String.format("%s completed", methodName));
1124                }
1125                removeReceiver(receiver);
1126                return;
1127            }
1128            sleep(POLL_TIME);
1129        }
1130
1131        int firedFlags = receiver.getFiredFlags();
1132        removeReceiver(receiver);
1133        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)",
1134                methodName, state, BluetoothPan.STATE_CONNECTED, firedFlags, mask));
1135    }
1136
1137    /**
1138     * Disconnects the PANU from a remote NAP and checks to make sure that the PANU is disconnected
1139     * and that the correct actions were broadcast.
1140     *
1141     * @param adapter The BT adapter.
1142     * @param device The remote device.
1143     */
1144    public void disconnectPan(BluetoothAdapter adapter, BluetoothDevice device) {
1145        disconnectFromRemoteOrVerifyConnectNap(adapter, device, true);
1146    }
1147
1148    /**
1149     * Checks that a remote PANU disconnects from the local NAP correctly and that the correct
1150     * actions were broadcast.
1151     *
1152     * @param adapter The BT adapter.
1153     * @param device The remote device.
1154     */
1155    public void incomingPanDisconnection(BluetoothAdapter adapter, BluetoothDevice device) {
1156        disconnectFromRemoteOrVerifyConnectNap(adapter, device, false);
1157    }
1158
1159    /**
1160     * Helper method used by {@link #disconnectPan(BluetoothAdapter, BluetoothDevice)} and
1161     * {@link #incomingPanDisconnection(BluetoothAdapter, BluetoothDevice)} to either disconnect
1162     * from a remote NAP or verify that a remote device disconnected from the local NAP.
1163     *
1164     * @param adapter The BT adapter.
1165     * @param device The remote device.
1166     * @param disconnect Whether the method should connect or verify.
1167     */
1168    private void disconnectFromRemoteOrVerifyConnectNap(BluetoothAdapter adapter,
1169            BluetoothDevice device, boolean disconnect) {
1170        long start = -1;
1171        int mask, role;
1172        String methodName;
1173
1174        if (disconnect) {
1175            methodName = String.format("disconnectPan(device=%s)", device);
1176            mask = (ConnectProfileReceiver.STATE_DISCONNECTED_FLAG |
1177                    ConnectProfileReceiver.STATE_DISCONNECTING_FLAG);
1178            role = BluetoothPan.LOCAL_PANU_ROLE;
1179        } else {
1180            methodName = String.format("incomingPanDisconnection(device=%s)", device);
1181            mask = ConnectProfileReceiver.STATE_DISCONNECTED_FLAG;
1182            role = BluetoothPan.LOCAL_NAP_ROLE;
1183        }
1184
1185        if (!adapter.isEnabled()) {
1186            fail(String.format("%s bluetooth not enabled", methodName));
1187        }
1188
1189        if (!adapter.getBondedDevices().contains(device)) {
1190            fail(String.format("%s device not paired", methodName));
1191        }
1192
1193        mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN);
1194        assertNotNull(mPan);
1195        ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask);
1196
1197        int state = mPan.getConnectionState(device);
1198        switch (state) {
1199            case BluetoothPan.STATE_CONNECTED:
1200            case BluetoothPan.STATE_CONNECTING:
1201                start = System.currentTimeMillis();
1202                if (role == BluetoothPan.LOCAL_PANU_ROLE) {
1203                    assertTrue(mPan.disconnect(device));
1204                }
1205                break;
1206            case BluetoothPan.STATE_DISCONNECTED:
1207                removeReceiver(receiver);
1208                return;
1209            case BluetoothPan.STATE_DISCONNECTING:
1210                mask = 0; // Don't check for received intents since we might have missed them.
1211                break;
1212            default:
1213                removeReceiver(receiver);
1214                fail(String.format("%s invalid state: state=%d", methodName, state));
1215        }
1216
1217        long s = System.currentTimeMillis();
1218        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
1219            state = mPan.getConnectionState(device);
1220            if (state == BluetoothInputDevice.STATE_DISCONNECTED
1221                    && (receiver.getFiredFlags() & mask) == mask) {
1222                long finish = receiver.getCompletedTime();
1223                if (start != -1 && finish != -1) {
1224                    writeOutput(String.format("%s completed in %d ms", methodName,
1225                            (finish - start)));
1226                } else {
1227                    writeOutput(String.format("%s completed", methodName));
1228                }
1229                removeReceiver(receiver);
1230                return;
1231            }
1232            sleep(POLL_TIME);
1233        }
1234
1235        int firedFlags = receiver.getFiredFlags();
1236        removeReceiver(receiver);
1237        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)",
1238                methodName, state, BluetoothInputDevice.STATE_DISCONNECTED, firedFlags, mask));
1239    }
1240
1241    /**
1242     * Opens a SCO channel using {@link android.media.AudioManager#startBluetoothSco()} and checks
1243     * to make sure that the channel is opened and that the correct actions were broadcast.
1244     *
1245     * @param adapter The BT adapter.
1246     * @param device The remote device.
1247     */
1248    public void startSco(BluetoothAdapter adapter, BluetoothDevice device) {
1249        startStopSco(adapter, device, true);
1250    }
1251
1252    /**
1253     * Closes a SCO channel using {@link android.media.AudioManager#stopBluetoothSco()} and checks
1254     *  to make sure that the channel is closed and that the correct actions were broadcast.
1255     *
1256     * @param adapter The BT adapter.
1257     * @param device The remote device.
1258     */
1259    public void stopSco(BluetoothAdapter adapter, BluetoothDevice device) {
1260        startStopSco(adapter, device, false);
1261    }
1262    /**
1263     * Helper method for {@link #startSco(BluetoothAdapter, BluetoothDevice)} and
1264     * {@link #stopSco(BluetoothAdapter, BluetoothDevice)}.
1265     *
1266     * @param adapter The BT adapter.
1267     * @param device The remote device.
1268     * @param isStart Whether the SCO channel should be opened.
1269     */
1270    private void startStopSco(BluetoothAdapter adapter, BluetoothDevice device, boolean isStart) {
1271        long start = -1;
1272        int mask;
1273        String methodName;
1274
1275        if (isStart) {
1276            methodName = String.format("startSco(device=%s)", device);
1277            mask = StartStopScoReceiver.STATE_CONNECTED_FLAG;
1278        } else {
1279            methodName = String.format("stopSco(device=%s)", device);
1280            mask = StartStopScoReceiver.STATE_DISCONNECTED_FLAG;
1281        }
1282
1283        if (!adapter.isEnabled()) {
1284            fail(String.format("%s bluetooth not enabled", methodName));
1285        }
1286
1287        if (!adapter.getBondedDevices().contains(device)) {
1288            fail(String.format("%s device not paired", methodName));
1289        }
1290
1291        AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
1292        assertNotNull(manager);
1293
1294        if (!manager.isBluetoothScoAvailableOffCall()) {
1295            fail(String.format("%s device does not support SCO", methodName));
1296        }
1297
1298        boolean isScoOn = manager.isBluetoothScoOn();
1299        if (isStart == isScoOn) {
1300            return;
1301        }
1302
1303        StartStopScoReceiver receiver = getStartStopScoReceiver(mask);
1304        start = System.currentTimeMillis();
1305        if (isStart) {
1306            manager.startBluetoothSco();
1307        } else {
1308            manager.stopBluetoothSco();
1309        }
1310
1311        long s = System.currentTimeMillis();
1312        while (System.currentTimeMillis() - s < START_STOP_SCO_TIMEOUT) {
1313            isScoOn = manager.isBluetoothScoOn();
1314            if (isStart == isScoOn && (receiver.getFiredFlags() & mask) == mask) {
1315                long finish = receiver.getCompletedTime();
1316                if (start != -1 && finish != -1) {
1317                    writeOutput(String.format("%s completed in %d ms", methodName,
1318                            (finish - start)));
1319                } else {
1320                    writeOutput(String.format("%s completed", methodName));
1321                }
1322                removeReceiver(receiver);
1323                return;
1324            }
1325            sleep(POLL_TIME);
1326        }
1327
1328        int firedFlags = receiver.getFiredFlags();
1329        removeReceiver(receiver);
1330        fail(String.format("%s timeout: on=%b (expected %b), flags=0x%x (expected 0x%x)",
1331                methodName, isScoOn, isStart, firedFlags, mask));
1332    }
1333
1334    /**
1335     * Writes a string to the logcat and a file if a file has been specified in the constructor.
1336     *
1337     * @param s The string to be written.
1338     */
1339    public void writeOutput(String s) {
1340        Log.i(mTag, s);
1341        if (mOutputWriter == null) {
1342            return;
1343        }
1344        try {
1345            mOutputWriter.write(s + "\n");
1346            mOutputWriter.flush();
1347        } catch (IOException e) {
1348            Log.w(mTag, "Could not write to output file", e);
1349        }
1350    }
1351
1352    private void addReceiver(BroadcastReceiver receiver, String[] actions) {
1353        IntentFilter filter = new IntentFilter();
1354        for (String action: actions) {
1355            filter.addAction(action);
1356        }
1357        mContext.registerReceiver(receiver, filter);
1358        mReceivers.add(receiver);
1359    }
1360
1361    private BluetoothReceiver getBluetoothReceiver(int expectedFlags) {
1362        String[] actions = {
1363                BluetoothAdapter.ACTION_DISCOVERY_FINISHED,
1364                BluetoothAdapter.ACTION_DISCOVERY_STARTED,
1365                BluetoothAdapter.ACTION_SCAN_MODE_CHANGED,
1366                BluetoothAdapter.ACTION_STATE_CHANGED};
1367        BluetoothReceiver receiver = new BluetoothReceiver(expectedFlags);
1368        addReceiver(receiver, actions);
1369        return receiver;
1370    }
1371
1372    private PairReceiver getPairReceiver(BluetoothDevice device, int passkey, byte[] pin,
1373            int expectedFlags) {
1374        String[] actions = {
1375                BluetoothDevice.ACTION_PAIRING_REQUEST,
1376                BluetoothDevice.ACTION_BOND_STATE_CHANGED};
1377        PairReceiver receiver = new PairReceiver(device, passkey, pin, expectedFlags);
1378        addReceiver(receiver, actions);
1379        return receiver;
1380    }
1381
1382    private ConnectProfileReceiver getConnectProfileReceiver(BluetoothDevice device, int profile,
1383            int expectedFlags) {
1384        String[] actions = {
1385                BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED,
1386                BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED,
1387                BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED};
1388        ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile,
1389                expectedFlags);
1390        addReceiver(receiver, actions);
1391        return receiver;
1392    }
1393
1394    private ConnectPanReceiver getConnectPanReceiver(BluetoothDevice device, int role,
1395            int expectedFlags) {
1396        String[] actions = {BluetoothPan.ACTION_CONNECTION_STATE_CHANGED};
1397        ConnectPanReceiver receiver = new ConnectPanReceiver(device, role, expectedFlags);
1398        addReceiver(receiver, actions);
1399        return receiver;
1400    }
1401
1402    private StartStopScoReceiver getStartStopScoReceiver(int expectedFlags) {
1403        String[] actions = {AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED};
1404        StartStopScoReceiver receiver = new StartStopScoReceiver(expectedFlags);
1405        addReceiver(receiver, actions);
1406        return receiver;
1407    }
1408
1409    private void removeReceiver(BroadcastReceiver receiver) {
1410        mContext.unregisterReceiver(receiver);
1411        mReceivers.remove(receiver);
1412    }
1413
1414    private BluetoothProfile connectProxy(BluetoothAdapter adapter, int profile) {
1415        switch (profile) {
1416            case BluetoothProfile.A2DP:
1417                if (mA2dp != null) {
1418                    return mA2dp;
1419                }
1420                break;
1421            case BluetoothProfile.HEADSET:
1422                if (mHeadset != null) {
1423                    return mHeadset;
1424                }
1425                break;
1426            case BluetoothProfile.INPUT_DEVICE:
1427                if (mInput != null) {
1428                    return mInput;
1429                }
1430                break;
1431            case BluetoothProfile.PAN:
1432                if (mPan != null) {
1433                    return mPan;
1434                }
1435                break;
1436            default:
1437                return null;
1438        }
1439        adapter.getProfileProxy(mContext, mServiceListener, profile);
1440        long s = System.currentTimeMillis();
1441        switch (profile) {
1442            case BluetoothProfile.A2DP:
1443                while (mA2dp == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1444                    sleep(POLL_TIME);
1445                }
1446                return mA2dp;
1447            case BluetoothProfile.HEADSET:
1448                while (mHeadset == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1449                    sleep(POLL_TIME);
1450                }
1451                return mHeadset;
1452            case BluetoothProfile.INPUT_DEVICE:
1453                while (mInput == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1454                    sleep(POLL_TIME);
1455                }
1456                return mInput;
1457            case BluetoothProfile.PAN:
1458                while (mPan == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1459                    sleep(POLL_TIME);
1460                }
1461                return mPan;
1462            default:
1463                return null;
1464        }
1465    }
1466
1467    private void sleep(long time) {
1468        try {
1469            Thread.sleep(time);
1470        } catch (InterruptedException e) {
1471        }
1472    }
1473}
1474