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