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