1. Download our Official Android App: Forums for Android!

Apps Programmatically bonding to BLE device: Pairing variant issue on android

Discussion in 'Android Development' started by daz89, Aug 9, 2017.

  1. daz89

    daz89 Lurker
    Thread Starter
    Rank:
    None
    Points:
    15
    Posts:
    3
    Joined:
    Aug 9, 2017

    Aug 9, 2017
    3
    1
    15
    Hi all,
    I am trying to build an app that is trimmed to have the best user experience. meaning that the user enters the pin code for his bluetooth device in the app.

    So I want to connect and bond to a bluetooth device in my app and not have to get it done by the system.

    There numerous examples of this done in nearly the same way depending on API versions.
    Android/Bluetooth programatically pairing options
    How to pair Bluetooth device programmatically Android

    This is the procedure that I found and followed

    1. Register to receive BluetoothDevice.ACTION_PAIRING_REQUEST changed broadcast intents. Use a high priority!
    2. Connect to the device.
    3. Discover services.
    4. If you have disconnected by now, it's probably because the bond information is incorrect (e.g. the peripheral purged it). In that case, delete the bond information using a hidden method (seriously Google), and reconnect.
    5. Try to read a characteristic that requires encryption MitM protection.
    6. In the ACTION_PAIRING_REQUEST broadcast receiver, check that the pairing type is BluetoothDevice.PAIRING_VARIANT_PIN and if so, call setPin() and abortBroadcast(). Otherwise you can just let the system handle it, or show an error or whatever
    source code at : Programmatically pairing with a BLE device on Android 4.4+
    Programatical pairing is possible by overriding the onReceive in the BroadcastReceiver callback and

    The issue :The only thing is with this example I arrive at the bonding stage but the pairing variant is not the right one.

    My personnel debug
     

    Advertisement

  2. daz89

    daz89 Lurker
    Thread Starter
    Rank:
    None
    Points:
    15
    Posts:
    3
    Joined:
    Aug 9, 2017

    Aug 9, 2017
    3
    1
    15
    I solved the issue with a lot of head sracthing. So the code above works even for android 7.1.1. I just formated my Pin wrong and I had moved the some parts of the code for the intent in the function on create.

    But the thing is the popup that is supposed to appear asking for confirmation for the association does so in the notifcation bar. And then the Pin is entered programaticaly.
    Now how can I realy make the popup make it do what it's name says?
     
  3. LV426

    LV426 I say we take off and nuke this place from orbit
    Moderator
    Rank:
     #11
    Points:
    1,988
    Posts:
    7,866
    Joined:
    Oct 16, 2015

    Oct 16, 2015
    7,866
    11,495
    1,988
    Male
    Software developer
    South West of England
    Can you clarify what bit of code you are referring to, by pasting in this thread?
     
  4. daz89

    daz89 Lurker
    Thread Starter
    Rank:
    None
    Points:
    15
    Posts:
    3
    Joined:
    Aug 9, 2017

    Aug 9, 2017
    3
    1
    15
    This portion of code I put in a button on click event
    Code (Java):
    1. // Actually set it in response to ACTION_PAIRING_REQUEST.
    2. final IntentFilter pairingRequestFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST);
    3. pairingRequestFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY - 1);
    4. getActivity().getApplicationContext().registerReceiver(mPairingRequestRecevier, pairingRequestFilter);
    5. connectGatt();
    This is the entire code used as a fragement that I used in an activity instead.
    Code (Java):
    1. /* This implements the BLE connection logic. Things to watch out for:
    2.  
    3. 1. If the bond information is wrong (e.g. it has been deleted on the peripheral) then
    4.    discoverServices() will cause a disconnect. You need to delete the bonding information and reconnect.
    5.  
    6. 2. If the user ignores the PIN request, you get the undocumented GATT_AUTH_FAILED code.
    7.  
    8. */
    9. public class ConnectActivityLogic extends Fragment
    10. {
    11.     // The connection to the device, if we are connected.
    12.     private BluetoothGatt mGatt;
    13.  
    14.     // This is used to allow GUI fragments to subscribe to state change notifications.
    15.     public static class StateObservable extends Observable
    16.     {
    17.         private void notifyChanged() {
    18.             setChanged();
    19.             notifyObservers();
    20.         }
    21.     };
    22.  
    23.     // When the logic state changes, State.notifyObservers(this) is called.
    24.     public final StateObservable State = new StateObservable();
    25.  
    26.     public ConnectActivityLogic()
    27.     {
    28.     }
    29.  
    30.     @Override
    31.     public void onCreate(Bundle savedInstanceState)
    32.     {
    33.         super.onCreate(savedInstanceState);
    34.  
    35.         // Tell the framework to try to keep this fragment around
    36.         // during a configuration change.
    37.         setRetainInstance(true);
    38.  
    39.         // Actually set it in response to ACTION_PAIRING_REQUEST.
    40.         final IntentFilter pairingRequestFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST);
    41.         pairingRequestFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY - 1);
    42.         getActivity().getApplicationContext().registerReceiver(mPairingRequestRecevier, pairingRequestFilter);
    43.  
    44.         // Update the UI.
    45.         State.notifyChanged();
    46.  
    47.         // Note that we don't actually need to request permission - all apps get BLUETOOTH and BLUETOOTH_ADMIN permissions.
    48.         // LOCATION_COARSE is only used for scanning which I don't need (MAC is hard-coded).
    49.  
    50.         // Connect to the device.
    51.         connectGatt();
    52.     }
    53.  
    54.     @Override
    55.     public void onDestroy()
    56.     {
    57.         super.onDestroy();
    58.  
    59.         // Disconnect from the device if we're still connected.
    60.         disconnectGatt();
    61.  
    62.         // Unregister the broadcast receiver.
    63.         getActivity().getApplicationContext().unregisterReceiver(mPairingRequestRecevier);
    64.     }
    65.  
    66.     // The state used by the UI to show connection progress.
    67.     public ConnectionState getConnectionState()
    68.     {
    69.         return mState;
    70.     }
    71.  
    72.     // Internal state machine.
    73.     public enum ConnectionState
    74.     {
    75.         IDLE,
    76.         CONNECT_GATT,
    77.         DISCOVER_SERVICES,
    78.         READ_CHARACTERISTIC,
    79.         FAILED,
    80.         SUCCEEDED,
    81.     }
    82.     private ConnectionState mState = ConnectionState.IDLE;
    83.  
    84.     // When this fragment is created it is given the MAC address and PIN to connect to.
    85.     public byte[] macAddress()
    86.     {
    87.         return getArguments().getByteArray("mac");
    88.     }
    89.     public int pinCode()
    90.     {
    91.         return getArguments().getInt("pin", -1);
    92.     }
    93.  
    94.     // Start the connection process.
    95.     private void connectGatt()
    96.     {
    97.         // Disconnect if we are already connected.
    98.         disconnectGatt();
    99.  
    100.         // Update state.
    101.         mState = ConnectionState.CONNECT_GATT;
    102.         State.notifyChanged();
    103.  
    104.         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress());
    105.  
    106.         // Connect!
    107.         mGatt = device.connectGatt(getActivity(), false, mBleCallback);
    108.     }
    109.  
    110.     private void disconnectGatt()
    111.     {
    112.         if (mGatt != null)
    113.         {
    114.             mGatt.disconnect();
    115.             mGatt.close();
    116.             mGatt = null;
    117.         }
    118.     }
    119.  
    120.     // See https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/master/stack/include/gatt_api.h
    121.     private static final int GATT_ERROR = 0x85;
    122.     private static final int GATT_AUTH_FAIL = 0x89;
    123.  
    124.     private android.bluetooth.BluetoothGattCallback mBleCallback = new BluetoothGattCallback()
    125.     {
    126.         @Override
    127.         public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
    128.         {
    129.             super.onConnectionStateChange(gatt, status, newState);
    130.             switch (newState)
    131.             {
    132.             case BluetoothProfile.STATE_CONNECTED:
    133.                 // Connected to the device. Try to discover services.
    134.                 if (gatt.discoverServices())
    135.                 {
    136.                     // Update state.
    137.                     mState = ConnectionState.DISCOVER_SERVICES;
    138.                     State.notifyChanged();
    139.                 }
    140.                 else
    141.                 {
    142.                     // Couldn't discover services for some reason. Fail.
    143.                     disconnectGatt();
    144.                     mState = ConnectionState.FAILED;
    145.                     State.notifyChanged();
    146.                 }
    147.                 break;
    148.             case BluetoothProfile.STATE_DISCONNECTED:
    149.                 // If we try to discover services while bonded it seems to disconnect.
    150.                 // We need to debond and rebond...
    151.  
    152.                 switch (mState)
    153.                 {
    154.                     case IDLE:
    155.                         // Do nothing in this case.
    156.                         break;
    157.                     case CONNECT_GATT:
    158.                         // This can happen if the bond information is incorrect. Delete it and reconnect.
    159.                         deleteBondInformation(gatt.getDevice());
    160.                         connectGatt();
    161.                         break;
    162.                     case DISCOVER_SERVICES:
    163.                         // This can also happen if the bond information is incorrect. Delete it and reconnect.
    164.                         deleteBondInformation(gatt.getDevice());
    165.                         connectGatt();
    166.                         break;
    167.                     case READ_CHARACTERISTIC:
    168.                         // Disconnected while reading the characteristic. Probably just a link failure.
    169.                         gatt.close();
    170.                         mState = ConnectionState.FAILED;
    171.                         State.notifyChanged();
    172.                         break;
    173.                     case FAILED:
    174.                     case SUCCEEDED:
    175.                         // Normal disconnection.
    176.                         break;
    177.                 }
    178.                 break;
    179.             }
    180.         }
    181.  
    182.         @Override
    183.         public void onServicesDiscovered(BluetoothGatt gatt, int status)
    184.         {
    185.             super.onServicesDiscovered(gatt, status);
    186.  
    187.             // Services have been discovered. Now I try to read a characteristic that requires MitM protection.
    188.             // This triggers pairing and bonding.
    189.  
    190.             BluetoothGattService nameService = gatt.getService(UUIDs.NAME_SERVICE);
    191.             if (nameService == null)
    192.             {
    193.                 // Service not found.
    194.                 disconnectGatt();
    195.                 mState = ConnectionState.FAILED;
    196.                 State.notifyChanged();
    197.                 return;
    198.             }
    199.             BluetoothGattCharacteristic characteristic = nameService.getCharacteristic(UUIDs.NAME_CHARACTERISTIC);
    200.             if (characteristic == null)
    201.             {
    202.                 // Characteristic not found.
    203.                 disconnectGatt();
    204.                 mState = ConnectionState.FAILED;
    205.                 State.notifyChanged();
    206.                 return;
    207.             }
    208.  
    209.             // Read the characteristic.
    210.             gatt.readCharacteristic(characteristic);
    211.             mState = ConnectionState.READ_CHARACTERISTIC;
    212.             State.notifyChanged();
    213.         }
    214.  
    215.         @Override
    216.         public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)
    217.         {
    218.             super.onCharacteristicRead(gatt, characteristic, status);
    219.  
    220.             if (status == BluetoothGatt.GATT_SUCCESS)
    221.             {
    222.                 // Characteristic read. Check it is the right one.
    223.                 if (!UUIDs.NAME_CHARACTERISTIC.equals(characteristic.getUuid()))
    224.                 {
    225.                     // Read the wrong characteristic. This shouldn't happen.
    226.                     disconnectGatt();
    227.                     mState = ConnectionState.FAILED;
    228.                     State.notifyChanged();
    229.                     return;
    230.                 }
    231.  
    232.                 // Get the name (the characteristic I am reading just contains the device name).
    233.                 byte[] value = characteristic.getValue();
    234.                 if (value == null)
    235.                 {
    236.                     // Hmm...
    237.                 }
    238.  
    239.                 disconnectGatt();
    240.                 mState = ConnectionState.SUCCEEDED;
    241.                 State.notifyChanged();
    242.  
    243.                 // Success! Save it to the database or whatever...
    244.             }
    245.             else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION)
    246.             {
    247.                 // This is where the tricky part comes
    248.                 if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_NONE)
    249.                 {
    250.                     // Bonding required.
    251.                     // The broadcast receiver should be called.
    252.                 }
    253.                 else
    254.                 {
    255.                     // ?
    256.                 }
    257.             }
    258.             else if (status == GATT_AUTH_FAIL)
    259.             {
    260.                 // This can happen because the user ignored the pairing request notification for too long.
    261.                 // Or presumably if they put the wrong PIN in.
    262.                 disconnectGatt();
    263.                 mState = ConnectionState.FAILED;
    264.                 State.notifyChanged();
    265.             }
    266.             else if (status == GATT_ERROR)
    267.             {
    268.                 // I thought this happened if the bond information was wrong, but now I'm not sure.
    269.                 disconnectGatt();
    270.                 mState = ConnectionState.FAILED;
    271.                 State.notifyChanged();
    272.             }
    273.             else
    274.             {
    275.                 // That's weird.
    276.                 disconnectGatt();
    277.                 mState = ConnectionState.FAILED;
    278.                 State.notifyChanged();
    279.             }
    280.         }
    281.     };
    282.  
    283.  
    284.     private final BroadcastReceiver mPairingRequestRecevier = new BroadcastReceiver()
    285.     {
    286.         @Override
    287.         public void onReceive(Context context, Intent intent)
    288.         {
    289.             if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction()))
    290.             {
    291.                 final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    292.                 int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
    293.  
    294.                 if (type == BluetoothDevice.PAIRING_VARIANT_PIN)
    295.                 {
    296.                     device.setPin(Util.IntToPasskey(pinCode()));
    297.                     abortBroadcast();
    298.                 }
    299.                 else
    300.                 {
    301.                     L.w("Unexpected pairing type: " + type);
    302.                 }
    303.             }
    304.         }
    305.     };
    306.  
    307.     public static void deleteBondInformation(BluetoothDevice device)
    308.     {
    309.         try
    310.         {
    311.             // FFS Google, just unhide the method.
    312.             Method m = device.getClass().getMethod("removeBond", (Class[]) null);
    313.             m.invoke(device, (Object[]) null);
    314.         }
    315.         catch (Exception e)
    316.         {
    317.             L.e(e.getMessage());
    318.         }
    319.     }
    320. }
    Also the string to byte for the PIN that works best is
    Code (Java):
    1. device.setPin("123456".getBytes());
    whereas before I used these methods to do the same
    Code (Java):
    1.     //The following functions are used to convert Pins to ARRAY of bytes or vice-versa
    2.     public static  byte[] my_int_to_bb_le(int myInteger){
    3.         return ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN).putInt(myInteger).array();
    4.     }
    5.  
    6.     public static int my_bb_to_int_le(byte [] byteBarray){
    7.         return ByteBuffer.wrap(byteBarray).order(ByteOrder.LITTLE_ENDIAN).getInt();
    8.     }
    9.  
    10.     public static  byte[] IntToPasskey_be(int myInteger){
    11.         return ByteBuffer.allocate(6).order(ByteOrder.BIG_ENDIAN).putInt(myInteger).array();
    12.     }
    13.  
    14.     public static int my_bb_to_int_be(byte [] byteBarray){
    15.         return ByteBuffer.wrap(byteBarray).order(ByteOrder.BIG_ENDIAN).getInt();
    16.     }
    Another issue is knowing if the PIN you entered was correct or not. The only way of knowing is seeing if you are bonded. But I dont know of a way to capture an error in the Pin code.
     
    #4 daz89, Aug 11, 2017
    Last edited: Aug 11, 2017
    sweetndreemy73 likes this.

Share This Page

Loading...