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