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.BluetoothHeadset.ServiceListener;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.os.Environment;
25import android.util.Log;
26
27import junit.framework.Assert;
28
29import java.io.BufferedWriter;
30import java.io.File;
31import java.io.FileWriter;
32import java.io.IOException;
33import java.util.ArrayList;
34import java.util.List;
35
36public class BluetoothTestUtils extends Assert {
37
38    /**
39     * Timeout for {@link BluetoothAdapter#disable()} in ms.
40     */
41    private static final int DISABLE_TIMEOUT = 20000;
42
43    /**
44     * Timeout for {@link BluetoothAdapter#enable()} in ms.
45     */
46    private static final int ENABLE_TIMEOUT = 20000;
47
48    /**
49     * Timeout for {@link BluetoothAdapter#setScanMode(int)} in ms.
50     */
51    private static final int SET_SCAN_MODE_TIMEOUT = 5000;
52
53    /**
54     * Timeout for {@link BluetoothAdapter#startDiscovery()} in ms.
55     */
56    private static final int START_DISCOVERY_TIMEOUT = 5000;
57
58    /**
59     * Timeout for {@link BluetoothAdapter#cancelDiscovery()} in ms.
60     */
61    private static final int CANCEL_DISCOVERY_TIMEOUT = 5000;
62
63    /**
64     * Timeout for {@link BluetoothDevice#createBond()} in ms.
65     */
66    private static final int PAIR_TIMEOUT = 20000;
67
68    /**
69     * Timeout for {@link BluetoothDevice#removeBond()} in ms.
70     */
71    private static final int UNPAIR_TIMEOUT = 20000;
72
73    /**
74     * Timeout for {@link BluetoothA2dp#connectSink(BluetoothDevice)} in ms.
75     */
76    private static final int CONNECT_A2DP_TIMEOUT = 20000;
77
78    /**
79     * Timeout for {@link BluetoothA2dp#disconnectSink(BluetoothDevice)} in ms.
80     */
81    private static final int DISCONNECT_A2DP_TIMEOUT = 20000;
82
83    /**
84     * Timeout for {@link BluetoothHeadset#connectHeadset(BluetoothDevice)} in ms.
85     */
86    private static final int CONNECT_HEADSET_TIMEOUT = 20000;
87
88    /**
89     * Timeout for {@link BluetoothHeadset#disconnectHeadset(BluetoothDevice)} in ms.
90     */
91    private static final int DISCONNECT_HEADSET_TIMEOUT = 20000;
92
93    /**
94     * Time between polls in ms.
95     */
96    private static final int POLL_TIME = 100;
97
98    private Context mContext;
99
100    private BufferedWriter mOutputWriter;
101
102    private BluetoothA2dp mA2dp;
103
104    private BluetoothHeadset mHeadset;
105
106    private String mOutputFile;
107    private String mTag;
108    private class HeadsetServiceListener implements ServiceListener {
109        private boolean mConnected = false;
110
111        public void onServiceConnected() {
112            synchronized (this) {
113                mConnected = true;
114            }
115        }
116
117        public void onServiceDisconnected() {
118            synchronized (this) {
119                mConnected = false;
120            }
121        }
122
123        public boolean isConnected() {
124            synchronized (this) {
125                return mConnected;
126            }
127        }
128    }
129
130    private HeadsetServiceListener mHeadsetServiceListener = new HeadsetServiceListener();
131
132    private class BluetoothReceiver extends BroadcastReceiver {
133        private static final int DISCOVERY_STARTED_FLAG = 1;
134        private static final int DISCOVERY_FINISHED_FLAG = 1 << 1;
135        private static final int SCAN_MODE_NONE_FLAG = 1 << 2;
136        private static final int SCAN_MODE_CONNECTABLE_FLAG = 1 << 3;
137        private static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG = 1 << 4;
138        private static final int STATE_OFF_FLAG = 1 << 5;
139        private static final int STATE_TURNING_ON_FLAG = 1 << 6;
140        private static final int STATE_ON_FLAG = 1 << 7;
141        private static final int STATE_TURNING_OFF_FLAG = 1 << 8;
142        private static final int PROFILE_A2DP_FLAG = 1 << 9;
143        private static final int PROFILE_HEADSET_FLAG = 1 << 10;
144
145        private static final int A2DP_STATE_DISCONNECTED = 1;
146        private static final int A2DP_STATE_CONNECTING = 1 << 1;
147        private static final int A2DP_STATE_CONNECTED = 1 << 2;
148        private static final int A2DP_STATE_DISCONNECTING = 1 << 3;
149        private static final int A2DP_STATE_PLAYING = 1 << 4;
150
151        private static final int HEADSET_STATE_DISCONNECTED = 1;
152        private static final int HEADSET_STATE_CONNECTING = 1 << 1;
153        private static final int HEADSET_STATE_CONNECTED = 1 << 2;
154
155        private int mFiredFlags = 0;
156        private int mA2dpFiredFlags = 0;
157        private int mHeadsetFiredFlags = 0;
158
159        @Override
160        public void onReceive(Context context, Intent intent) {
161            synchronized (this) {
162                if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) {
163                    mFiredFlags |= DISCOVERY_STARTED_FLAG;
164                } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) {
165                    mFiredFlags |= DISCOVERY_FINISHED_FLAG;
166                } else if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) {
167                    int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE,
168                            BluetoothAdapter.ERROR);
169                    assertNotSame(mode, BluetoothAdapter.ERROR);
170                    switch (mode) {
171                        case BluetoothAdapter.SCAN_MODE_NONE:
172                            mFiredFlags |= SCAN_MODE_NONE_FLAG;
173                            break;
174                        case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
175                            mFiredFlags |= SCAN_MODE_CONNECTABLE_FLAG;
176                            break;
177                        case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
178                            mFiredFlags |= SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG;
179                            break;
180                    }
181                } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
182                    int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
183                            BluetoothAdapter.ERROR);
184                    assertNotSame(state, BluetoothAdapter.ERROR);
185                    switch (state) {
186                        case BluetoothAdapter.STATE_OFF:
187                            mFiredFlags |= STATE_OFF_FLAG;
188                            break;
189                        case BluetoothAdapter.STATE_TURNING_ON:
190                            mFiredFlags |= STATE_TURNING_ON_FLAG;
191                            break;
192                        case BluetoothAdapter.STATE_ON:
193                            mFiredFlags |= STATE_ON_FLAG;
194                            break;
195                        case BluetoothAdapter.STATE_TURNING_OFF:
196                            mFiredFlags |= STATE_TURNING_OFF_FLAG;
197                            break;
198                    }
199                } else if (BluetoothA2dp.ACTION_SINK_STATE_CHANGED.equals(intent.getAction())) {
200                    mFiredFlags |= PROFILE_A2DP_FLAG;
201                    int state = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, -1);
202                    assertNotSame(state, -1);
203                    switch (state) {
204                        case BluetoothA2dp.STATE_DISCONNECTED:
205                            mA2dpFiredFlags |= A2DP_STATE_DISCONNECTED;
206                            break;
207                        case BluetoothA2dp.STATE_CONNECTING:
208                            mA2dpFiredFlags |= A2DP_STATE_CONNECTING;
209                            break;
210                        case BluetoothA2dp.STATE_CONNECTED:
211                            mA2dpFiredFlags |= A2DP_STATE_CONNECTED;
212                            break;
213                        case BluetoothA2dp.STATE_DISCONNECTING:
214                            mA2dpFiredFlags |= A2DP_STATE_DISCONNECTING;
215                            break;
216                        case BluetoothA2dp.STATE_PLAYING:
217                            mA2dpFiredFlags |= A2DP_STATE_PLAYING;
218                            break;
219                    }
220                } else if (BluetoothHeadset.ACTION_STATE_CHANGED.equals(intent.getAction())) {
221                    mFiredFlags |= PROFILE_HEADSET_FLAG;
222                    int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
223                            BluetoothHeadset.STATE_ERROR);
224                    assertNotSame(state, BluetoothHeadset.STATE_ERROR);
225                    switch (state) {
226                        case BluetoothHeadset.STATE_DISCONNECTED:
227                            mHeadsetFiredFlags |= HEADSET_STATE_DISCONNECTED;
228                            break;
229                        case BluetoothHeadset.STATE_CONNECTING:
230                            mHeadsetFiredFlags |= HEADSET_STATE_CONNECTING;
231                            break;
232                        case BluetoothHeadset.STATE_CONNECTED:
233                            mHeadsetFiredFlags |= HEADSET_STATE_CONNECTED;
234                            break;
235                    }
236                }
237            }
238        }
239
240        public int getFiredFlags() {
241            synchronized (this) {
242                return mFiredFlags;
243            }
244        }
245
246        public int getA2dpFiredFlags() {
247            synchronized (this) {
248                return mA2dpFiredFlags;
249            }
250        }
251
252        public int getHeadsetFiredFlags() {
253            synchronized (this) {
254                return mHeadsetFiredFlags;
255            }
256        }
257
258        public void resetFiredFlags() {
259            synchronized (this) {
260                mFiredFlags = 0;
261                mA2dpFiredFlags = 0;
262                mHeadsetFiredFlags = 0;
263            }
264        }
265    }
266
267    private BluetoothReceiver mBluetoothReceiver = new BluetoothReceiver();
268
269    private class PairReceiver extends BroadcastReceiver {
270        private final static int PAIR_FLAG = 1;
271        private static final int PAIR_STATE_BONDED = 1;
272        private static final int PAIR_STATE_BONDING = 1 << 1;
273        private static final int PAIR_STATE_NONE = 1 << 2;
274
275        private int mFiredFlags = 0;
276        private int mPairFiredFlags = 0;
277
278        private BluetoothDevice mDevice;
279        private int mPasskey;
280        private byte[] mPin;
281
282        public PairReceiver(BluetoothDevice device, int passkey, byte[] pin) {
283            super();
284            mDevice = device;
285            mPasskey = passkey;
286            mPin = pin;
287        }
288
289        @Override
290        public void onReceive(Context context, Intent intent) {
291            synchronized (this) {
292                if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())
293                        && mDevice.equals(intent.getParcelableExtra(
294                                BluetoothDevice.EXTRA_DEVICE))) {
295                    int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
296                            BluetoothDevice.ERROR);
297                    assertNotSame(type, BluetoothDevice.ERROR);
298                    switch (type) {
299                        case BluetoothDevice.PAIRING_VARIANT_PIN:
300                            mDevice.setPin(mPin);
301                            break;
302                        case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
303                            mDevice.setPasskey(mPasskey);
304                            break;
305                        case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
306                        case BluetoothDevice.PAIRING_VARIANT_CONSENT:
307                            mDevice.setPairingConfirmation(true);
308                            break;
309                        case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
310                            mDevice.setRemoteOutOfBandData();
311                            break;
312                    }
313                } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())
314                        && mDevice.equals(intent.getParcelableExtra(
315                                BluetoothDevice.EXTRA_DEVICE))) {
316                    mFiredFlags |= PAIR_FLAG;
317                    int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
318                            BluetoothDevice.ERROR);
319                    assertNotSame(state, BluetoothDevice.ERROR);
320                    switch (state) {
321                        case BluetoothDevice.BOND_BONDED:
322                            mPairFiredFlags |= PAIR_STATE_BONDED;
323                            break;
324                        case BluetoothDevice.BOND_BONDING:
325                            mPairFiredFlags |= PAIR_STATE_BONDING;
326                            break;
327                        case BluetoothDevice.BOND_NONE:
328                            mPairFiredFlags |= PAIR_STATE_NONE;
329                            break;
330                    }
331                }
332            }
333        }
334
335        public int getFiredFlags() {
336            synchronized (this) {
337                return mFiredFlags;
338            }
339        }
340
341        public int getPairFiredFlags() {
342            synchronized (this) {
343                return mPairFiredFlags;
344            }
345        }
346
347        public void resetFiredFlags() {
348            synchronized (this) {
349                mFiredFlags = 0;
350                mPairFiredFlags = 0;
351            }
352        }
353    }
354
355    private List<BroadcastReceiver> mReceivers = new ArrayList<BroadcastReceiver>();
356
357    public BluetoothTestUtils(Context context, String tag) {
358        this(context, tag, null);
359    }
360
361    public BluetoothTestUtils(Context context, String tag, String outputFile) {
362        mContext = context;
363        mTag = tag;
364        mOutputFile = outputFile;
365
366        if (mOutputFile == null) {
367            mOutputWriter = null;
368        } else {
369            try {
370                mOutputWriter = new BufferedWriter(new FileWriter(new File(
371                        Environment.getExternalStorageDirectory(), mOutputFile), true));
372            } catch (IOException e) {
373                Log.w(mTag, "Test output file could not be opened", e);
374                mOutputWriter = null;
375            }
376        }
377
378        mA2dp = new BluetoothA2dp(mContext);
379        mHeadset = new BluetoothHeadset(mContext, mHeadsetServiceListener);
380        mBluetoothReceiver = getBluetoothReceiver(mContext);
381        mReceivers.add(mBluetoothReceiver);
382    }
383
384    public void close() {
385        while (!mReceivers.isEmpty()) {
386            mContext.unregisterReceiver(mReceivers.remove(0));
387        }
388
389        if (mOutputWriter != null) {
390            try {
391                mOutputWriter.close();
392            } catch (IOException e) {
393                Log.w(mTag, "Test output file could not be closed", e);
394            }
395        }
396    }
397
398    public void enable(BluetoothAdapter adapter) {
399        int mask = (BluetoothReceiver.STATE_TURNING_ON_FLAG | BluetoothReceiver.STATE_ON_FLAG
400                | BluetoothReceiver.SCAN_MODE_CONNECTABLE_FLAG);
401        mBluetoothReceiver.resetFiredFlags();
402
403        int state = adapter.getState();
404        switch (state) {
405            case BluetoothAdapter.STATE_ON:
406                assertTrue(adapter.isEnabled());
407                return;
408            case BluetoothAdapter.STATE_OFF:
409            case BluetoothAdapter.STATE_TURNING_OFF:
410                assertFalse(adapter.isEnabled());
411                assertTrue(adapter.enable());
412                break;
413            case BluetoothAdapter.STATE_TURNING_ON:
414                assertFalse(adapter.isEnabled());
415                mask = 0; // Don't check for received intents since we might have missed them.
416                break;
417            default:
418                fail("enable() invalid state: state=" + state);
419        }
420
421        long s = System.currentTimeMillis();
422        while (System.currentTimeMillis() - s < ENABLE_TIMEOUT) {
423            state = adapter.getState();
424            if (state == BluetoothAdapter.STATE_ON) {
425                assertTrue(adapter.isEnabled());
426                if ((mBluetoothReceiver.getFiredFlags() & mask) == mask) {
427                    mBluetoothReceiver.resetFiredFlags();
428                    writeOutput(String.format("enable() completed in %d ms",
429                            (System.currentTimeMillis() - s)));
430                    return;
431                }
432            } else {
433                assertFalse(adapter.isEnabled());
434                assertEquals(BluetoothAdapter.STATE_TURNING_ON, state);
435            }
436            sleep(POLL_TIME);
437        }
438
439        int firedFlags = mBluetoothReceiver.getFiredFlags();
440        mBluetoothReceiver.resetFiredFlags();
441        fail(String.format("enable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
442                state, BluetoothAdapter.STATE_ON, firedFlags, mask));
443    }
444
445    public void disable(BluetoothAdapter adapter) {
446        int mask = (BluetoothReceiver.STATE_TURNING_OFF_FLAG | BluetoothReceiver.STATE_OFF_FLAG
447                | BluetoothReceiver.SCAN_MODE_NONE_FLAG);
448        mBluetoothReceiver.resetFiredFlags();
449
450        int state = adapter.getState();
451        switch (state) {
452            case BluetoothAdapter.STATE_OFF:
453                assertFalse(adapter.isEnabled());
454                return;
455            case BluetoothAdapter.STATE_ON:
456                assertTrue(adapter.isEnabled());
457                assertTrue(adapter.disable());
458                break;
459            case BluetoothAdapter.STATE_TURNING_ON:
460                assertFalse(adapter.isEnabled());
461                assertTrue(adapter.disable());
462                break;
463            case BluetoothAdapter.STATE_TURNING_OFF:
464                assertFalse(adapter.isEnabled());
465                mask = 0; // Don't check for received intents since we might have missed them.
466                break;
467            default:
468                fail("disable() invalid state: state=" + state);
469        }
470
471        long s = System.currentTimeMillis();
472        while (System.currentTimeMillis() - s < DISABLE_TIMEOUT) {
473            state = adapter.getState();
474            if (state == BluetoothAdapter.STATE_OFF) {
475                assertFalse(adapter.isEnabled());
476                if ((mBluetoothReceiver.getFiredFlags() & mask) == mask) {
477                    mBluetoothReceiver.resetFiredFlags();
478                    writeOutput(String.format("disable() completed in %d ms",
479                            (System.currentTimeMillis() - s)));
480                    return;
481                }
482            } else {
483                assertFalse(adapter.isEnabled());
484                assertEquals(BluetoothAdapter.STATE_TURNING_OFF, state);
485            }
486            sleep(POLL_TIME);
487        }
488
489        int firedFlags = mBluetoothReceiver.getFiredFlags();
490        mBluetoothReceiver.resetFiredFlags();
491        fail(String.format("disable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
492                state, BluetoothAdapter.STATE_OFF, firedFlags, mask));
493    }
494
495    public void discoverable(BluetoothAdapter adapter) {
496        int mask = BluetoothReceiver.SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG;
497        mBluetoothReceiver.resetFiredFlags();
498
499        if (!adapter.isEnabled()) {
500            fail("discoverable() bluetooth not enabled");
501        }
502
503        int scanMode = adapter.getScanMode();
504        if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
505            return;
506        }
507
508        assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE);
509        assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE));
510
511        long s = System.currentTimeMillis();
512        while (System.currentTimeMillis() - s < SET_SCAN_MODE_TIMEOUT) {
513            scanMode = adapter.getScanMode();
514            if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
515                if ((mBluetoothReceiver.getFiredFlags() & mask) == mask) {
516                    mBluetoothReceiver.resetFiredFlags();
517                    writeOutput(String.format("discoverable() completed in %d ms",
518                            (System.currentTimeMillis() - s)));
519                    return;
520                }
521            } else {
522                assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE);
523            }
524            sleep(POLL_TIME);
525        }
526
527        int firedFlags = mBluetoothReceiver.getFiredFlags();
528        mBluetoothReceiver.resetFiredFlags();
529        fail(String.format("discoverable() timeout: scanMode=%d (expected %d), flags=0x%x "
530                + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,
531                firedFlags, mask));
532    }
533
534    public void undiscoverable(BluetoothAdapter adapter) {
535        int mask = BluetoothReceiver.SCAN_MODE_CONNECTABLE_FLAG;
536        mBluetoothReceiver.resetFiredFlags();
537
538        if (!adapter.isEnabled()) {
539            fail("undiscoverable() bluetooth not enabled");
540        }
541
542        int scanMode = adapter.getScanMode();
543        if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) {
544            return;
545        }
546
547        assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
548        assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE));
549
550        long s = System.currentTimeMillis();
551        while (System.currentTimeMillis() - s < SET_SCAN_MODE_TIMEOUT) {
552            scanMode = adapter.getScanMode();
553            if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) {
554                if ((mBluetoothReceiver.getFiredFlags() & mask) == mask) {
555                    mBluetoothReceiver.resetFiredFlags();
556                    writeOutput(String.format("undiscoverable() completed in %d ms",
557                            (System.currentTimeMillis() - s)));
558                    return;
559                }
560            } else {
561                assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
562            }
563            sleep(POLL_TIME);
564        }
565
566        int firedFlags = mBluetoothReceiver.getFiredFlags();
567        mBluetoothReceiver.resetFiredFlags();
568        fail(String.format("undiscoverable() timeout: scanMode=%d (expected %d), flags=0x%x "
569                + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE, firedFlags,
570                mask));
571    }
572
573    public void startScan(BluetoothAdapter adapter) {
574        int mask = BluetoothReceiver.DISCOVERY_STARTED_FLAG;
575        mBluetoothReceiver.resetFiredFlags();
576
577        if (!adapter.isEnabled()) {
578            fail("startScan() bluetooth not enabled");
579        }
580
581        if (adapter.isDiscovering()) {
582            return;
583        }
584
585        assertTrue(adapter.startDiscovery());
586
587        long s = System.currentTimeMillis();
588        while (System.currentTimeMillis() - s < START_DISCOVERY_TIMEOUT) {
589            if (adapter.isDiscovering() && ((mBluetoothReceiver.getFiredFlags() & mask) == mask)) {
590                mBluetoothReceiver.resetFiredFlags();
591                writeOutput(String.format("startScan() completed in %d ms",
592                        (System.currentTimeMillis() - s)));
593                return;
594            }
595            sleep(POLL_TIME);
596        }
597
598        int firedFlags = mBluetoothReceiver.getFiredFlags();
599        mBluetoothReceiver.resetFiredFlags();
600        fail(String.format("startScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)",
601                adapter.isDiscovering(), firedFlags, mask));
602    }
603
604    public void stopScan(BluetoothAdapter adapter) {
605        int mask = BluetoothReceiver.DISCOVERY_FINISHED_FLAG;
606        mBluetoothReceiver.resetFiredFlags();
607
608        if (!adapter.isEnabled()) {
609            fail("stopScan() bluetooth not enabled");
610        }
611
612        if (!adapter.isDiscovering()) {
613            return;
614        }
615
616        // TODO: put assertTrue() around cancelDiscovery() once it starts returning true.
617        adapter.cancelDiscovery();
618
619        long s = System.currentTimeMillis();
620        while (System.currentTimeMillis() - s < CANCEL_DISCOVERY_TIMEOUT) {
621            if (!adapter.isDiscovering() && ((mBluetoothReceiver.getFiredFlags() & mask) == mask)) {
622                mBluetoothReceiver.resetFiredFlags();
623                writeOutput(String.format("stopScan() completed in %d ms",
624                        (System.currentTimeMillis() - s)));
625                return;
626            }
627            sleep(POLL_TIME);
628        }
629
630        int firedFlags = mBluetoothReceiver.getFiredFlags();
631        mBluetoothReceiver.resetFiredFlags();
632        fail(String.format("stopScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)",
633                adapter.isDiscovering(), firedFlags, mask));
634
635    }
636
637    public void pair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, byte[] pin) {
638        pairOrAcceptPair(adapter, device, passkey, pin, true);
639    }
640
641    public void acceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey,
642            byte[] pin) {
643        pairOrAcceptPair(adapter, device, passkey, pin, false);
644    }
645
646    private void pairOrAcceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey,
647            byte[] pin, boolean pair) {
648        String methodName = pair ? "pair()" : "acceptPair()";
649        int mask = PairReceiver.PAIR_FLAG;
650        int pairMask = PairReceiver.PAIR_STATE_BONDING | PairReceiver.PAIR_STATE_BONDED;
651
652        PairReceiver pairReceiver = getPairReceiver(mContext, device, passkey, pin);
653        mReceivers.add(pairReceiver);
654
655        if (!adapter.isEnabled()) {
656            fail(methodName + " bluetooth not enabled");
657        }
658
659        int state = device.getBondState();
660        switch (state) {
661            case BluetoothDevice.BOND_BONDED:
662                assertTrue(adapter.getBondedDevices().contains(device));
663                return;
664            case BluetoothDevice.BOND_BONDING:
665                // Don't check for received intents since we might have missed them.
666                mask = pairMask = 0;
667                break;
668            case BluetoothDevice.BOND_NONE:
669                assertFalse(adapter.getBondedDevices().contains(device));
670                if (pair) {
671                    assertTrue(device.createBond());
672                }
673                break;
674            default:
675                fail(methodName + " invalide state: state=" + state);
676        }
677
678        long s = System.currentTimeMillis();
679        while (System.currentTimeMillis() - s < PAIR_TIMEOUT) {
680            state = device.getBondState();
681            if (state == BluetoothDevice.BOND_BONDED) {
682                assertTrue(adapter.getBondedDevices().contains(device));
683                if ((pairReceiver.getFiredFlags() & mask) == mask
684                        && (pairReceiver.getPairFiredFlags() & pairMask) == pairMask) {
685                    writeOutput(String.format("%s completed in %d ms: device=%s",
686                            methodName, (System.currentTimeMillis() - s), device));
687                    mReceivers.remove(pairReceiver);
688                    mContext.unregisterReceiver(pairReceiver);
689                    return;
690                }
691            }
692            sleep(POLL_TIME);
693        }
694
695        int firedFlags = pairReceiver.getFiredFlags();
696        int pairFiredFlags = pairReceiver.getPairFiredFlags();
697        pairReceiver.resetFiredFlags();
698        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x), "
699                + "pairFlags=0x%x (expected 0x%x)", methodName, state, BluetoothDevice.BOND_BONDED,
700                firedFlags, mask, pairFiredFlags, pairMask));
701    }
702
703    public void unpair(BluetoothAdapter adapter, BluetoothDevice device) {
704        int mask = PairReceiver.PAIR_FLAG;
705        int pairMask = PairReceiver.PAIR_STATE_NONE;
706
707        PairReceiver pairReceiver = getPairReceiver(mContext, device, 0, null);
708        mReceivers.add(pairReceiver);
709
710        if (!adapter.isEnabled()) {
711            fail("unpair() bluetooth not enabled");
712        }
713
714        int state = device.getBondState();
715        switch (state) {
716            case BluetoothDevice.BOND_BONDED:
717                assertTrue(adapter.getBondedDevices().contains(device));
718                assertTrue(device.removeBond());
719                break;
720            case BluetoothDevice.BOND_BONDING:
721                assertTrue(device.removeBond());
722                break;
723            case BluetoothDevice.BOND_NONE:
724                assertFalse(adapter.getBondedDevices().contains(device));
725                return;
726            default:
727                fail("unpair() invalid state: state=" + state);
728        }
729
730        assertTrue(device.removeBond());
731
732        long s = System.currentTimeMillis();
733        while (System.currentTimeMillis() - s < UNPAIR_TIMEOUT) {
734            if (device.getBondState() == BluetoothDevice.BOND_NONE) {
735                assertFalse(adapter.getBondedDevices().contains(device));
736                if ((pairReceiver.getFiredFlags() & mask) == mask
737                       && (pairReceiver.getPairFiredFlags() & pairMask) == pairMask) {
738                    writeOutput(String.format("unpair() completed in %d ms: device=%s",
739                            (System.currentTimeMillis() - s), device));
740                    mReceivers.remove(pairReceiver);
741                    mContext.unregisterReceiver(pairReceiver);
742                    return;
743                }
744            }
745        }
746
747        int firedFlags = pairReceiver.getFiredFlags();
748        int pairFiredFlags = pairReceiver.getPairFiredFlags();
749        pairReceiver.resetFiredFlags();
750        fail(String.format("unpair() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x), "
751                + "pairFlags=0x%x (expected 0x%x)", state, BluetoothDevice.BOND_BONDED, firedFlags,
752                mask, pairFiredFlags, pairMask));
753    }
754
755    public void connectA2dp(BluetoothAdapter adapter, BluetoothDevice device) {
756        int mask = BluetoothReceiver.PROFILE_A2DP_FLAG;
757        int a2dpMask1 = (BluetoothReceiver.A2DP_STATE_CONNECTING
758                | BluetoothReceiver.A2DP_STATE_CONNECTED | BluetoothReceiver.A2DP_STATE_PLAYING);
759        int a2dpMask2 = a2dpMask1 ^ BluetoothReceiver.A2DP_STATE_CONNECTED;
760        int a2dpMask3 = a2dpMask1 ^ BluetoothReceiver.A2DP_STATE_PLAYING;
761        mBluetoothReceiver.resetFiredFlags();
762
763        if (!adapter.isEnabled()) {
764            fail("connectA2dp() bluetooth not enabled");
765        }
766
767        if (!adapter.getBondedDevices().contains(device)) {
768            fail("connectA2dp() device not paired: device=" + device);
769        }
770
771        int state = mA2dp.getSinkState(device);
772        switch (state) {
773            case BluetoothA2dp.STATE_CONNECTED:
774            case BluetoothA2dp.STATE_PLAYING:
775                assertTrue(mA2dp.isSinkConnected(device));
776                return;
777            case BluetoothA2dp.STATE_DISCONNECTING:
778            case BluetoothA2dp.STATE_DISCONNECTED:
779                assertFalse(mA2dp.isSinkConnected(device));
780                assertTrue(mA2dp.connectSink(device));
781                break;
782            case BluetoothA2dp.STATE_CONNECTING:
783                assertFalse(mA2dp.isSinkConnected(device));
784                // Don't check for received intents since we might have missed them.
785                mask = a2dpMask1 = a2dpMask2 = a2dpMask3 = 0;
786                break;
787            default:
788                fail("connectA2dp() invalid state: state=" + state);
789        }
790
791        long s = System.currentTimeMillis();
792        while (System.currentTimeMillis() - s < CONNECT_A2DP_TIMEOUT) {
793            state = mA2dp.getSinkState(device);
794            if (state == BluetoothA2dp.STATE_CONNECTED || state == BluetoothA2dp.STATE_PLAYING) {
795                assertTrue(mA2dp.isSinkConnected(device));
796                // Check whether STATE_CONNECTING and (STATE_CONNECTED or STATE_PLAYING) intents
797                // have fired if we are checking if intents should be fired.
798                int firedFlags = mBluetoothReceiver.getFiredFlags();
799                int a2dpFiredFlags = mBluetoothReceiver.getA2dpFiredFlags();
800                if ((mBluetoothReceiver.getFiredFlags() & mask) == mask
801                        && ((a2dpFiredFlags & a2dpMask1) == a2dpMask1
802                                || (a2dpFiredFlags & a2dpMask2) == a2dpMask2
803                                || (a2dpFiredFlags & a2dpMask3) == a2dpMask3)) {
804                    mBluetoothReceiver.resetFiredFlags();
805                    writeOutput(String.format("connectA2dp() completed in %d ms: device=%s",
806                            (System.currentTimeMillis() - s), device));
807                    return;
808                }
809            }
810            sleep(POLL_TIME);
811        }
812
813        int firedFlags = mBluetoothReceiver.getFiredFlags();
814        int a2dpFiredFlags = mBluetoothReceiver.getA2dpFiredFlags();
815        mBluetoothReceiver.resetFiredFlags();
816        fail(String.format("connectA2dp() timeout: state=%d (expected %d or %d), "
817                + "flags=0x%x (expected 0x%x), a2dpFlags=0x%x (expected 0x%x or 0x%x or 0x%x)",
818                state, BluetoothHeadset.STATE_CONNECTED, BluetoothA2dp.STATE_PLAYING, firedFlags,
819                mask, a2dpFiredFlags, a2dpMask1, a2dpMask2, a2dpMask3));
820    }
821
822    public void disconnectA2dp(BluetoothAdapter adapter, BluetoothDevice device) {
823        int mask = BluetoothReceiver.PROFILE_A2DP_FLAG;
824        int a2dpMask = (BluetoothReceiver.A2DP_STATE_DISCONNECTING
825                | BluetoothReceiver.A2DP_STATE_DISCONNECTED);
826        mBluetoothReceiver.resetFiredFlags();
827
828        if (!adapter.isEnabled()) {
829            fail("disconnectA2dp() bluetooth not enabled");
830        }
831
832        if (!adapter.getBondedDevices().contains(device)) {
833            fail("disconnectA2dp() device not paired: device=" + device);
834        }
835
836        int state = mA2dp.getSinkState(device);
837        switch (state) {
838            case BluetoothA2dp.STATE_DISCONNECTED:
839                assertFalse(mA2dp.isSinkConnected(device));
840                return;
841            case BluetoothA2dp.STATE_CONNECTED:
842            case BluetoothA2dp.STATE_PLAYING:
843                assertTrue(mA2dp.isSinkConnected(device));
844                assertTrue(mA2dp.disconnectSink(device));
845                break;
846            case BluetoothA2dp.STATE_CONNECTING:
847                assertFalse(mA2dp.isSinkConnected(device));
848                assertTrue(mA2dp.disconnectSink(device));
849                break;
850            case BluetoothA2dp.STATE_DISCONNECTING:
851                assertFalse(mA2dp.isSinkConnected(device));
852                // Don't check for received intents since we might have missed them.
853                mask = a2dpMask = 0;
854                break;
855            default:
856                fail("disconnectA2dp() invalid state: state=" + state);
857        }
858
859        long s = System.currentTimeMillis();
860        while (System.currentTimeMillis() - s < DISCONNECT_A2DP_TIMEOUT) {
861            state = mA2dp.getSinkState(device);
862            if (state == BluetoothA2dp.STATE_DISCONNECTED) {
863                assertFalse(mA2dp.isSinkConnected(device));
864                if ((mBluetoothReceiver.getFiredFlags() & mask) == mask
865                        && (mBluetoothReceiver.getA2dpFiredFlags() & a2dpMask) == a2dpMask) {
866                    mBluetoothReceiver.resetFiredFlags();
867                    writeOutput(String.format("disconnectA2dp() completed in %d ms: device=%s",
868                            (System.currentTimeMillis() - s), device));
869                    return;
870                }
871            }
872            sleep(POLL_TIME);
873        }
874
875        int firedFlags = mBluetoothReceiver.getFiredFlags();
876        int a2dpFiredFlags = mBluetoothReceiver.getA2dpFiredFlags();
877        mBluetoothReceiver.resetFiredFlags();
878        fail(String.format("disconnectA2dp() timeout: state=%d (expected %d), "
879                + "flags=0x%x (expected 0x%x), a2dpFlags=0x%x (expected 0x%x)", state,
880                BluetoothA2dp.STATE_DISCONNECTED, firedFlags, mask, a2dpFiredFlags, a2dpMask));
881    }
882
883    public void connectHeadset(BluetoothAdapter adapter, BluetoothDevice device) {
884        int mask = BluetoothReceiver.PROFILE_HEADSET_FLAG;
885        int headsetMask = (BluetoothReceiver.HEADSET_STATE_CONNECTING
886                | BluetoothReceiver.HEADSET_STATE_CONNECTED);
887        mBluetoothReceiver.resetFiredFlags();
888
889        if (!adapter.isEnabled()) {
890            fail("connectHeadset() bluetooth not enabled");
891        }
892
893        if (!adapter.getBondedDevices().contains(device)) {
894            fail("connectHeadset() device not paired: device=" + device);
895        }
896
897        while (!mHeadsetServiceListener.isConnected()) {
898            sleep(POLL_TIME);
899        }
900
901        int state = mHeadset.getState(device);
902        switch (state) {
903            case BluetoothHeadset.STATE_CONNECTED:
904                assertTrue(mHeadset.isConnected(device));
905                return;
906            case BluetoothHeadset.STATE_DISCONNECTED:
907                assertFalse(mHeadset.isConnected(device));
908                mHeadset.connectHeadset(device);
909                break;
910            case BluetoothHeadset.STATE_CONNECTING:
911                assertFalse(mHeadset.isConnected(device));
912                // Don't check for received intents since we might have missed them.
913                mask = headsetMask = 0;
914                break;
915            case BluetoothHeadset.STATE_ERROR:
916                fail("connectHeadset() error state");
917                break;
918            default:
919                fail("connectHeadset() invalid state: state=" + state);
920        }
921
922        long s = System.currentTimeMillis();
923        while (System.currentTimeMillis() - s < CONNECT_HEADSET_TIMEOUT) {
924            state = mHeadset.getState(device);
925            if (state == BluetoothHeadset.STATE_CONNECTED) {
926                assertTrue(mHeadset.isConnected(device));
927                if ((mBluetoothReceiver.getFiredFlags() & mask) == mask
928                        && (mBluetoothReceiver.getHeadsetFiredFlags() & headsetMask) == headsetMask) {
929                    mBluetoothReceiver.resetFiredFlags();
930                    writeOutput(String.format("connectHeadset() completed in %d ms: device=%s",
931                            (System.currentTimeMillis() - s), device));
932                    return;
933                }
934            }
935            sleep(POLL_TIME);
936        }
937
938        int firedFlags = mBluetoothReceiver.getFiredFlags();
939        int headsetFiredFlags = mBluetoothReceiver.getHeadsetFiredFlags();
940        mBluetoothReceiver.resetFiredFlags();
941        fail(String.format("connectHeadset() timeout: state=%d (expected %d), "
942                + "flags=0x%x (expected 0x%x), headsetFlags=0x%s (expected 0x%x)", state,
943                BluetoothHeadset.STATE_CONNECTED, firedFlags, mask, headsetFiredFlags,
944                headsetMask));
945    }
946
947    public void disconnectHeadset(BluetoothAdapter adapter, BluetoothDevice device) {
948        int mask = BluetoothReceiver.PROFILE_HEADSET_FLAG;
949        int headsetMask = BluetoothReceiver.HEADSET_STATE_DISCONNECTED;
950        mBluetoothReceiver.resetFiredFlags();
951
952        if (!adapter.isEnabled()) {
953            fail("disconnectHeadset() bluetooth not enabled");
954        }
955
956        if (!adapter.getBondedDevices().contains(device)) {
957            fail("disconnectHeadset() device not paired: device=" + device);
958        }
959
960        while (!mHeadsetServiceListener.isConnected()) {
961            sleep(POLL_TIME);
962        }
963
964        int state = mHeadset.getState(device);
965        switch (state) {
966            case BluetoothHeadset.STATE_CONNECTED:
967                mHeadset.disconnectHeadset(device);
968                break;
969            case BluetoothHeadset.STATE_CONNECTING:
970                mHeadset.disconnectHeadset(device);
971                break;
972            case BluetoothHeadset.STATE_DISCONNECTED:
973                return;
974            case BluetoothHeadset.STATE_ERROR:
975                fail("disconnectHeadset() error state");
976                break;
977            default:
978                fail("disconnectHeadset() invalid state: state=" + state);
979        }
980
981        long s = System.currentTimeMillis();
982        while (System.currentTimeMillis() - s < DISCONNECT_HEADSET_TIMEOUT) {
983            state = mHeadset.getState(device);
984            if (state == BluetoothHeadset.STATE_DISCONNECTED) {
985                assertFalse(mHeadset.isConnected(device));
986                if ((mBluetoothReceiver.getFiredFlags() & mask) == mask
987                        && (mBluetoothReceiver.getHeadsetFiredFlags() & headsetMask) == headsetMask) {
988                    mBluetoothReceiver.resetFiredFlags();
989                    writeOutput(String.format("disconnectHeadset() completed in %d ms: device=%s",
990                            (System.currentTimeMillis() - s), device));
991                    return;
992                }
993            }
994            sleep(POLL_TIME);
995        }
996
997        int firedFlags = mBluetoothReceiver.getFiredFlags();
998        int headsetFiredFlags = mBluetoothReceiver.getHeadsetFiredFlags();
999        mBluetoothReceiver.resetFiredFlags();
1000        fail(String.format("disconnectHeadset() timeout: state=%d (expected %d), "
1001                + "flags=0x%x (expected 0x%x), headsetFlags=0x%s (expected 0x%x)", state,
1002                BluetoothHeadset.STATE_DISCONNECTED, firedFlags, mask, headsetFiredFlags,
1003                headsetMask));
1004    }
1005
1006    public void writeOutput(String s) {
1007        Log.i(mTag, s);
1008        if (mOutputWriter == null) {
1009            return;
1010        }
1011        try {
1012            mOutputWriter.write(s + "\n");
1013            mOutputWriter.flush();
1014        } catch (IOException e) {
1015            Log.w(mTag, "Could not write to output file", e);
1016        }
1017    }
1018
1019    private BluetoothReceiver getBluetoothReceiver(Context context) {
1020        BluetoothReceiver receiver = new BluetoothReceiver();
1021        IntentFilter filter = new IntentFilter();
1022        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
1023        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
1024        filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
1025        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
1026        filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
1027        filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
1028        context.registerReceiver(receiver, filter);
1029        return receiver;
1030    }
1031
1032    private PairReceiver getPairReceiver(Context context, BluetoothDevice device, int passkey,
1033            byte[] pin) {
1034        PairReceiver receiver = new PairReceiver(device, passkey, pin);
1035        IntentFilter filter = new IntentFilter();
1036        filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
1037        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
1038        context.registerReceiver(receiver, filter);
1039        return receiver;
1040    }
1041
1042    private void sleep(long time) {
1043        try {
1044            Thread.sleep(time);
1045        } catch (InterruptedException e) {
1046        }
1047    }
1048}
1049