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