OSDN Git Service

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