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