Home | History | Annotate | Download | only in bluetooth
      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 
     65         public FlagReceiver(int expectedFlags) {
     66             mExpectedFlags = expectedFlags;
     67         }
     68 
     69         public int getFiredFlags() {
     70             synchronized (this) {
     71                 return mFiredFlags;
     72             }
     73         }
     74 
     75         public long getCompletedTime() {
     76             synchronized (this) {
     77                 return mCompletedTime;
     78             }
     79         }
     80 
     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 
    102         public BluetoothReceiver(int expectedFlags) {
    103             super(expectedFlags);
    104         }
    105 
    106         @Override
    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 
    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
    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 
    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.INPUT_DEVICE:
    231                     mConnectionAction = BluetoothInputDevice.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
    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 
    271         public ConnectPanReceiver(BluetoothDevice device, int role, int expectedFlags) {
    272             super(device, BluetoothProfile.PAN, expectedFlags);
    273 
    274             mRole = role;
    275         }
    276 
    277         @Override
    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 
    291         public StartStopScoReceiver(int expectedFlags) {
    292             super(expectedFlags);
    293         }
    294 
    295         @Override
    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.INPUT_DEVICE:
    326                         mInput = (BluetoothInputDevice) 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.INPUT_DEVICE:
    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 BluetoothInputDevice 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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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#INPUT_DEVICE}.
    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      */
    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.INPUT_DEVICE) {
    939                     assertTrue(((BluetoothInputDevice)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#INPUT_DEVICE}.
    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      */
    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.INPUT_DEVICE) {
   1014                     assertTrue(((BluetoothInputDevice)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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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 == BluetoothInputDevice.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, BluetoothInputDevice.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      */
   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      */
   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      */
   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      */
   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 
   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 
   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 
   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 
   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                 BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED};
   1408         ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile,
   1409                 expectedFlags);
   1410         addReceiver(receiver, actions);
   1411         return receiver;
   1412     }
   1413 
   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 
   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 
   1429     private void removeReceiver(BroadcastReceiver receiver) {
   1430         mContext.unregisterReceiver(receiver);
   1431         mReceivers.remove(receiver);
   1432     }
   1433 
   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.INPUT_DEVICE:
   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.INPUT_DEVICE:
   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 
   1487     private void sleep(long time) {
   1488         try {
   1489             Thread.sleep(time);
   1490         } catch (InterruptedException e) {
   1491         }
   1492     }
   1493 }
   1494