BluetoothTestUtils.java revision 7af75afb2c8608dbe73509036eefd3281d646a5f
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                assertTrue(proxy.connect(device));
940                break;
941            default:
942                removeReceiver(receiver);
943                fail(String.format("%s invalid state: state=%d", methodName, state));
944        }
945
946        long s = System.currentTimeMillis();
947        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
948            state = proxy.getConnectionState(device);
949            if (state == BluetoothProfile.STATE_CONNECTED
950                    && (receiver.getFiredFlags() & mask) == mask) {
951                long finish = receiver.getCompletedTime();
952                if (start != -1 && finish != -1) {
953                    writeOutput(String.format("%s completed in %d ms", methodName,
954                            (finish - start)));
955                } else {
956                    writeOutput(String.format("%s completed", methodName));
957                }
958                removeReceiver(receiver);
959                return;
960            }
961            sleep(POLL_TIME);
962        }
963
964        int firedFlags = receiver.getFiredFlags();
965        removeReceiver(receiver);
966        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
967                methodName, state, BluetoothProfile.STATE_CONNECTED, firedFlags, mask));
968    }
969
970    /**
971     * Disconnects a profile between the local device and a remote device and checks to make sure
972     * that the profile is disconnected and that the correct actions were broadcast.
973     *
974     * @param adapter The BT adapter.
975     * @param device The remote device.
976     * @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP},
977     * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}.
978     * @param methodName The method name to printed in the logs.  If null, will be
979     * "connectProfile(profile=&lt;profile&gt;, device=&lt;device&gt;)"
980     */
981    public void disconnectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile,
982            String methodName) {
983        if (methodName == null) {
984            methodName = String.format("disconnectProfile(profile=%d, device=%s)", profile, device);
985        }
986        int mask = (ConnectProfileReceiver.STATE_DISCONNECTING_FLAG
987                | ConnectProfileReceiver.STATE_DISCONNECTED_FLAG);
988        long start = -1;
989
990        if (!adapter.isEnabled()) {
991            fail(String.format("%s bluetooth not enabled", methodName));
992        }
993
994        if (!adapter.getBondedDevices().contains(device)) {
995            fail(String.format("%s device not paired", methodName));
996        }
997
998        BluetoothProfile proxy = connectProxy(adapter, profile);
999        assertNotNull(proxy);
1000
1001        ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask);
1002
1003        int state = proxy.getConnectionState(device);
1004        switch (state) {
1005            case BluetoothProfile.STATE_CONNECTED:
1006            case BluetoothProfile.STATE_CONNECTING:
1007                start = System.currentTimeMillis();
1008                assertTrue(proxy.disconnect(device));
1009                break;
1010            case BluetoothProfile.STATE_DISCONNECTED:
1011                removeReceiver(receiver);
1012                return;
1013            case BluetoothProfile.STATE_DISCONNECTING:
1014                mask = 0; // Don't check for received intents since we might have missed them.
1015                break;
1016            default:
1017                removeReceiver(receiver);
1018                fail(String.format("%s invalid state: state=%d", methodName, state));
1019        }
1020
1021        long s = System.currentTimeMillis();
1022        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
1023            state = proxy.getConnectionState(device);
1024            if (state == BluetoothProfile.STATE_DISCONNECTED
1025                    && (receiver.getFiredFlags() & mask) == mask) {
1026                long finish = receiver.getCompletedTime();
1027                if (start != -1 && finish != -1) {
1028                    writeOutput(String.format("%s completed in %d ms", methodName,
1029                            (finish - start)));
1030                } else {
1031                    writeOutput(String.format("%s completed", methodName));
1032                }
1033                removeReceiver(receiver);
1034                return;
1035            }
1036            sleep(POLL_TIME);
1037        }
1038
1039        int firedFlags = receiver.getFiredFlags();
1040        removeReceiver(receiver);
1041        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
1042                methodName, state, BluetoothProfile.STATE_DISCONNECTED, firedFlags, mask));
1043    }
1044
1045    /**
1046     * Connects the PANU to a remote NAP and checks to make sure that the PANU is connected and that
1047     * the correct actions were broadcast.
1048     *
1049     * @param adapter The BT adapter.
1050     * @param device The remote device.
1051     */
1052    public void connectPan(BluetoothAdapter adapter, BluetoothDevice device) {
1053        connectPanOrIncomingPanConnection(adapter, device, true);
1054    }
1055
1056    /**
1057     * Checks that a remote PANU connects to the local NAP correctly and that the correct actions
1058     * were broadcast.
1059     *
1060     * @param adapter The BT adapter.
1061     * @param device The remote device.
1062     */
1063    public void incomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device) {
1064        connectPanOrIncomingPanConnection(adapter, device, false);
1065    }
1066
1067    /**
1068     * Helper method used by {@link #connectPan(BluetoothAdapter, BluetoothDevice)} and
1069     * {@link #incomingPanConnection(BluetoothAdapter, BluetoothDevice)} to either connect to a
1070     * remote NAP or verify that a remote device connected to the local NAP.
1071     *
1072     * @param adapter The BT adapter.
1073     * @param device The remote device.
1074     * @param connect If the method should initiate the connection (is PANU)
1075     */
1076    private void connectPanOrIncomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device,
1077            boolean connect) {
1078        long start = -1;
1079        int mask, role;
1080        String methodName;
1081
1082        if (connect) {
1083            methodName = String.format("connectPan(device=%s)", device);
1084            mask = (ConnectProfileReceiver.STATE_CONNECTED_FLAG |
1085                    ConnectProfileReceiver.STATE_CONNECTING_FLAG);
1086            role = BluetoothPan.LOCAL_PANU_ROLE;
1087        } else {
1088            methodName = String.format("incomingPanConnection(device=%s)", device);
1089            mask = ConnectProfileReceiver.STATE_CONNECTED_FLAG;
1090            role = BluetoothPan.LOCAL_NAP_ROLE;
1091        }
1092
1093        if (!adapter.isEnabled()) {
1094            fail(String.format("%s bluetooth not enabled", methodName));
1095        }
1096
1097        if (!adapter.getBondedDevices().contains(device)) {
1098            fail(String.format("%s device not paired", methodName));
1099        }
1100
1101        mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN);
1102        assertNotNull(mPan);
1103        ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask);
1104
1105        int state = mPan.getConnectionState(device);
1106        switch (state) {
1107            case BluetoothPan.STATE_CONNECTED:
1108                removeReceiver(receiver);
1109                return;
1110            case BluetoothPan.STATE_CONNECTING:
1111                mask = 0; // Don't check for received intents since we might have missed them.
1112                break;
1113            case BluetoothPan.STATE_DISCONNECTED:
1114            case BluetoothPan.STATE_DISCONNECTING:
1115                start = System.currentTimeMillis();
1116                if (role == BluetoothPan.LOCAL_PANU_ROLE) {
1117                    Log.i("BT", "connect to pan");
1118                    assertTrue(mPan.connect(device));
1119                }
1120                break;
1121            default:
1122                removeReceiver(receiver);
1123                fail(String.format("%s invalid state: state=%d", methodName, state));
1124        }
1125
1126        long s = System.currentTimeMillis();
1127        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
1128            state = mPan.getConnectionState(device);
1129            if (state == BluetoothPan.STATE_CONNECTED
1130                    && (receiver.getFiredFlags() & mask) == mask) {
1131                long finish = receiver.getCompletedTime();
1132                if (start != -1 && finish != -1) {
1133                    writeOutput(String.format("%s completed in %d ms", methodName,
1134                            (finish - start)));
1135                } else {
1136                    writeOutput(String.format("%s completed", methodName));
1137                }
1138                removeReceiver(receiver);
1139                return;
1140            }
1141            sleep(POLL_TIME);
1142        }
1143
1144        int firedFlags = receiver.getFiredFlags();
1145        removeReceiver(receiver);
1146        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)",
1147                methodName, state, BluetoothPan.STATE_CONNECTED, firedFlags, mask));
1148    }
1149
1150    /**
1151     * Disconnects the PANU from a remote NAP and checks to make sure that the PANU is disconnected
1152     * and that the correct actions were broadcast.
1153     *
1154     * @param adapter The BT adapter.
1155     * @param device The remote device.
1156     */
1157    public void disconnectPan(BluetoothAdapter adapter, BluetoothDevice device) {
1158        disconnectFromRemoteOrVerifyConnectNap(adapter, device, true);
1159    }
1160
1161    /**
1162     * Checks that a remote PANU disconnects from the local NAP correctly and that the correct
1163     * actions were broadcast.
1164     *
1165     * @param adapter The BT adapter.
1166     * @param device The remote device.
1167     */
1168    public void incomingPanDisconnection(BluetoothAdapter adapter, BluetoothDevice device) {
1169        disconnectFromRemoteOrVerifyConnectNap(adapter, device, false);
1170    }
1171
1172    /**
1173     * Helper method used by {@link #disconnectPan(BluetoothAdapter, BluetoothDevice)} and
1174     * {@link #incomingPanDisconnection(BluetoothAdapter, BluetoothDevice)} to either disconnect
1175     * from a remote NAP or verify that a remote device disconnected from the local NAP.
1176     *
1177     * @param adapter The BT adapter.
1178     * @param device The remote device.
1179     * @param disconnect Whether the method should connect or verify.
1180     */
1181    private void disconnectFromRemoteOrVerifyConnectNap(BluetoothAdapter adapter,
1182            BluetoothDevice device, boolean disconnect) {
1183        long start = -1;
1184        int mask, role;
1185        String methodName;
1186
1187        if (disconnect) {
1188            methodName = String.format("disconnectPan(device=%s)", device);
1189            mask = (ConnectProfileReceiver.STATE_DISCONNECTED_FLAG |
1190                    ConnectProfileReceiver.STATE_DISCONNECTING_FLAG);
1191            role = BluetoothPan.LOCAL_PANU_ROLE;
1192        } else {
1193            methodName = String.format("incomingPanDisconnection(device=%s)", device);
1194            mask = ConnectProfileReceiver.STATE_DISCONNECTED_FLAG;
1195            role = BluetoothPan.LOCAL_NAP_ROLE;
1196        }
1197
1198        if (!adapter.isEnabled()) {
1199            fail(String.format("%s bluetooth not enabled", methodName));
1200        }
1201
1202        if (!adapter.getBondedDevices().contains(device)) {
1203            fail(String.format("%s device not paired", methodName));
1204        }
1205
1206        mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN);
1207        assertNotNull(mPan);
1208        ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask);
1209
1210        int state = mPan.getConnectionState(device);
1211        switch (state) {
1212            case BluetoothPan.STATE_CONNECTED:
1213            case BluetoothPan.STATE_CONNECTING:
1214                start = System.currentTimeMillis();
1215                if (role == BluetoothPan.LOCAL_PANU_ROLE) {
1216                    assertTrue(mPan.disconnect(device));
1217                }
1218                break;
1219            case BluetoothPan.STATE_DISCONNECTED:
1220                removeReceiver(receiver);
1221                return;
1222            case BluetoothPan.STATE_DISCONNECTING:
1223                mask = 0; // Don't check for received intents since we might have missed them.
1224                break;
1225            default:
1226                removeReceiver(receiver);
1227                fail(String.format("%s invalid state: state=%d", methodName, state));
1228        }
1229
1230        long s = System.currentTimeMillis();
1231        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
1232            state = mPan.getConnectionState(device);
1233            if (state == BluetoothInputDevice.STATE_DISCONNECTED
1234                    && (receiver.getFiredFlags() & mask) == mask) {
1235                long finish = receiver.getCompletedTime();
1236                if (start != -1 && finish != -1) {
1237                    writeOutput(String.format("%s completed in %d ms", methodName,
1238                            (finish - start)));
1239                } else {
1240                    writeOutput(String.format("%s completed", methodName));
1241                }
1242                removeReceiver(receiver);
1243                return;
1244            }
1245            sleep(POLL_TIME);
1246        }
1247
1248        int firedFlags = receiver.getFiredFlags();
1249        removeReceiver(receiver);
1250        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)",
1251                methodName, state, BluetoothInputDevice.STATE_DISCONNECTED, firedFlags, mask));
1252    }
1253
1254    /**
1255     * Opens a SCO channel using {@link android.media.AudioManager#startBluetoothSco()} and checks
1256     * to make sure that the channel is opened and that the correct actions were broadcast.
1257     *
1258     * @param adapter The BT adapter.
1259     * @param device The remote device.
1260     */
1261    public void startSco(BluetoothAdapter adapter, BluetoothDevice device) {
1262        startStopSco(adapter, device, true);
1263    }
1264
1265    /**
1266     * Closes a SCO channel using {@link android.media.AudioManager#stopBluetoothSco()} and checks
1267     *  to make sure that the channel is closed and that the correct actions were broadcast.
1268     *
1269     * @param adapter The BT adapter.
1270     * @param device The remote device.
1271     */
1272    public void stopSco(BluetoothAdapter adapter, BluetoothDevice device) {
1273        startStopSco(adapter, device, false);
1274    }
1275    /**
1276     * Helper method for {@link #startSco(BluetoothAdapter, BluetoothDevice)} and
1277     * {@link #stopSco(BluetoothAdapter, BluetoothDevice)}.
1278     *
1279     * @param adapter The BT adapter.
1280     * @param device The remote device.
1281     * @param isStart Whether the SCO channel should be opened.
1282     */
1283    private void startStopSco(BluetoothAdapter adapter, BluetoothDevice device, boolean isStart) {
1284        long start = -1;
1285        int mask;
1286        String methodName;
1287
1288        if (isStart) {
1289            methodName = String.format("startSco(device=%s)", device);
1290            mask = StartStopScoReceiver.STATE_CONNECTED_FLAG;
1291        } else {
1292            methodName = String.format("stopSco(device=%s)", device);
1293            mask = StartStopScoReceiver.STATE_DISCONNECTED_FLAG;
1294        }
1295
1296        if (!adapter.isEnabled()) {
1297            fail(String.format("%s bluetooth not enabled", methodName));
1298        }
1299
1300        if (!adapter.getBondedDevices().contains(device)) {
1301            fail(String.format("%s device not paired", methodName));
1302        }
1303
1304        AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
1305        assertNotNull(manager);
1306
1307        if (!manager.isBluetoothScoAvailableOffCall()) {
1308            fail(String.format("%s device does not support SCO", methodName));
1309        }
1310
1311        boolean isScoOn = manager.isBluetoothScoOn();
1312        if (isStart == isScoOn) {
1313            return;
1314        }
1315
1316        StartStopScoReceiver receiver = getStartStopScoReceiver(mask);
1317        start = System.currentTimeMillis();
1318        if (isStart) {
1319            manager.startBluetoothSco();
1320        } else {
1321            manager.stopBluetoothSco();
1322        }
1323
1324        long s = System.currentTimeMillis();
1325        while (System.currentTimeMillis() - s < START_STOP_SCO_TIMEOUT) {
1326            isScoOn = manager.isBluetoothScoOn();
1327            if (isStart == isScoOn && (receiver.getFiredFlags() & mask) == mask) {
1328                long finish = receiver.getCompletedTime();
1329                if (start != -1 && finish != -1) {
1330                    writeOutput(String.format("%s completed in %d ms", methodName,
1331                            (finish - start)));
1332                } else {
1333                    writeOutput(String.format("%s completed", methodName));
1334                }
1335                removeReceiver(receiver);
1336                return;
1337            }
1338            sleep(POLL_TIME);
1339        }
1340
1341        int firedFlags = receiver.getFiredFlags();
1342        removeReceiver(receiver);
1343        fail(String.format("%s timeout: on=%b (expected %b), flags=0x%x (expected 0x%x)",
1344                methodName, isScoOn, isStart, firedFlags, mask));
1345    }
1346
1347    /**
1348     * Writes a string to the logcat and a file if a file has been specified in the constructor.
1349     *
1350     * @param s The string to be written.
1351     */
1352    public void writeOutput(String s) {
1353        Log.i(mTag, s);
1354        if (mOutputWriter == null) {
1355            return;
1356        }
1357        try {
1358            mOutputWriter.write(s + "\n");
1359            mOutputWriter.flush();
1360        } catch (IOException e) {
1361            Log.w(mTag, "Could not write to output file", e);
1362        }
1363    }
1364
1365    private void addReceiver(BroadcastReceiver receiver, String[] actions) {
1366        IntentFilter filter = new IntentFilter();
1367        for (String action: actions) {
1368            filter.addAction(action);
1369        }
1370        mContext.registerReceiver(receiver, filter);
1371        mReceivers.add(receiver);
1372    }
1373
1374    private BluetoothReceiver getBluetoothReceiver(int expectedFlags) {
1375        String[] actions = {
1376                BluetoothAdapter.ACTION_DISCOVERY_FINISHED,
1377                BluetoothAdapter.ACTION_DISCOVERY_STARTED,
1378                BluetoothAdapter.ACTION_SCAN_MODE_CHANGED,
1379                BluetoothAdapter.ACTION_STATE_CHANGED};
1380        BluetoothReceiver receiver = new BluetoothReceiver(expectedFlags);
1381        addReceiver(receiver, actions);
1382        return receiver;
1383    }
1384
1385    private PairReceiver getPairReceiver(BluetoothDevice device, int passkey, byte[] pin,
1386            int expectedFlags) {
1387        String[] actions = {
1388                BluetoothDevice.ACTION_PAIRING_REQUEST,
1389                BluetoothDevice.ACTION_BOND_STATE_CHANGED};
1390        PairReceiver receiver = new PairReceiver(device, passkey, pin, expectedFlags);
1391        addReceiver(receiver, actions);
1392        return receiver;
1393    }
1394
1395    private ConnectProfileReceiver getConnectProfileReceiver(BluetoothDevice device, int profile,
1396            int expectedFlags) {
1397        String[] actions = {
1398                BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED,
1399                BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED,
1400                BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED};
1401        ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile,
1402                expectedFlags);
1403        addReceiver(receiver, actions);
1404        return receiver;
1405    }
1406
1407    private ConnectPanReceiver getConnectPanReceiver(BluetoothDevice device, int role,
1408            int expectedFlags) {
1409        String[] actions = {BluetoothPan.ACTION_CONNECTION_STATE_CHANGED};
1410        ConnectPanReceiver receiver = new ConnectPanReceiver(device, role, expectedFlags);
1411        addReceiver(receiver, actions);
1412        return receiver;
1413    }
1414
1415    private StartStopScoReceiver getStartStopScoReceiver(int expectedFlags) {
1416        String[] actions = {AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED};
1417        StartStopScoReceiver receiver = new StartStopScoReceiver(expectedFlags);
1418        addReceiver(receiver, actions);
1419        return receiver;
1420    }
1421
1422    private void removeReceiver(BroadcastReceiver receiver) {
1423        mContext.unregisterReceiver(receiver);
1424        mReceivers.remove(receiver);
1425    }
1426
1427    private BluetoothProfile connectProxy(BluetoothAdapter adapter, int profile) {
1428        switch (profile) {
1429            case BluetoothProfile.A2DP:
1430                if (mA2dp != null) {
1431                    return mA2dp;
1432                }
1433                break;
1434            case BluetoothProfile.HEADSET:
1435                if (mHeadset != null) {
1436                    return mHeadset;
1437                }
1438                break;
1439            case BluetoothProfile.INPUT_DEVICE:
1440                if (mInput != null) {
1441                    return mInput;
1442                }
1443                break;
1444            case BluetoothProfile.PAN:
1445                if (mPan != null) {
1446                    return mPan;
1447                }
1448                break;
1449            default:
1450                return null;
1451        }
1452        adapter.getProfileProxy(mContext, mServiceListener, profile);
1453        long s = System.currentTimeMillis();
1454        switch (profile) {
1455            case BluetoothProfile.A2DP:
1456                while (mA2dp == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1457                    sleep(POLL_TIME);
1458                }
1459                return mA2dp;
1460            case BluetoothProfile.HEADSET:
1461                while (mHeadset == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1462                    sleep(POLL_TIME);
1463                }
1464                return mHeadset;
1465            case BluetoothProfile.INPUT_DEVICE:
1466                while (mInput == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1467                    sleep(POLL_TIME);
1468                }
1469                return mInput;
1470            case BluetoothProfile.PAN:
1471                while (mPan == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
1472                    sleep(POLL_TIME);
1473                }
1474                return mPan;
1475            default:
1476                return null;
1477        }
1478    }
1479
1480    private void sleep(long time) {
1481        try {
1482            Thread.sleep(time);
1483        } catch (InterruptedException e) {
1484        }
1485    }
1486}
1487