1

This is a silly question, but I have a code which is giving me list of beacons with their names, addresses, uuids, majors and minors in TextView. I'm calculating uuid, major and minor values(in code below). And when I'm setting these three values into TextView they are swaping with other beacon values. So my question is, how do I set proper uuid, major and minor value to the proper beacon?

Link with the example: https://i.stack.imgur.com/vlepI.jpg

I have got two beacons. Names and adresses are correct, but as you can see uuid, major minor are the same and during scanning they keep swapping with each other beacon values.

Code is given below.

DeviceScanActivity

public class DeviceScanActivity extends ListActivity {

private LeDeviceListAdapter mLeDeviceListAdapter;
private BluetoothAdapter mBluetoothAdapter;
private boolean mScanning;
private Handler mHandler;
private static final int REQUEST_ENABLE_BT = 1;
private static final long SCAN_PERIOD = 10000;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getActionBar().setTitle(R.string.title_devices);
    mHandler = new Handler();

    if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
        Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
        finish();
    }

    final BluetoothManager bluetoothManager =
            (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    mBluetoothAdapter = bluetoothManager.getAdapter();

    if (mBluetoothAdapter == null) {
        Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();
        finish();
        return;
    }
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.menu_scan:
            mLeDeviceListAdapter.clear();
            scanLeDevice(true);
            break;
        case R.id.menu_stop:
            scanLeDevice(false);
            break;
    }
    return true;
}

@Override
protected void onResume() {
    super.onResume();

    if (!mBluetoothAdapter.isEnabled()) {
        if (!mBluetoothAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        }
    }

    mLeDeviceListAdapter = new LeDeviceListAdapter();
    setListAdapter(mLeDeviceListAdapter);
    scanLeDevice(true);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // User chose not to enable Bluetooth.
    if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) {
        finish();
        return;
    }
    super.onActivityResult(requestCode, resultCode, data);
}

@Override
protected void onPause() {
    super.onPause();
    scanLeDevice(false);
    mLeDeviceListAdapter.clear();
}

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
    final BluetoothDevice device = mLeDeviceListAdapter.getDevice(position);
    if (device == null) return;
    //final Intent intent = new Intent(this, DeviceControlActivity.class);

    if (mScanning) {
        mBluetoothAdapter.stopLeScan(mLeScanCallback);
        mScanning = false;
    }
    //startActivity(intent);
}

private void scanLeDevice(final boolean enable) {
    if (enable) {
        // Stops scanning after a pre-defined scan period.
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mScanning = false;
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
                invalidateOptionsMenu();
            }
        }, SCAN_PERIOD);

        mScanning = true;
        mBluetoothAdapter.startLeScan(mLeScanCallback);
    } else {
        mScanning = false;
        mBluetoothAdapter.stopLeScan(mLeScanCallback);
    }
    invalidateOptionsMenu();
}

private class LeDeviceListAdapter extends BaseAdapter {
    private ArrayList<BluetoothDevice> mLeDevices;
    private LayoutInflater mInflator;

    public LeDeviceListAdapter() {
        super();
        mLeDevices = new ArrayList<BluetoothDevice>();
        mInflator = DeviceScanActivity.this.getLayoutInflater();
    }

    public void addDevice(BluetoothDevice device) {
        if(!mLeDevices.contains(device)) {
            mLeDevices.add(device);
        }
    }

    public BluetoothDevice getDevice(int position) {
        return mLeDevices.get(position);
    }

    public void clear() {
        mLeDevices.clear();
    }

    @Override
    public int getCount() {
        return mLeDevices.size();
    }

    @Override
    public Object getItem(int i) {
        return mLeDevices.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        ViewHolder viewHolder;
        if (view == null) {
            view = mInflator.inflate(R.layout.listitem_device, null);
            viewHolder = new ViewHolder();
            viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address);
            viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name);
            viewHolder.deviceUUID = (TextView) view.findViewById(R.id.device_uuid);
            viewHolder.deviceMajor = (TextView) view.findViewById(R.id.device_major);
            viewHolder.deviceMinor = (TextView) view.findViewById(R.id.device_minor);
            view.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) view.getTag();
        }

        BluetoothDevice device = mLeDevices.get(i);
        final String deviceName = device.getName();
        if (deviceName != null && deviceName.length() > 0)
            viewHolder.deviceName.setText(deviceName);
        else
            viewHolder.deviceName.setText(R.string.unknown_device);
       viewHolder.deviceAddress.setText(device.getAddress());
        viewHolder.deviceUUID.setText(uuid);
        viewHolder.deviceMajor.setText(major);
        viewHolder.deviceMinor.setText(minor);

        return view;
    }
}

public String uuid;
public int major_temp;
public int minor_temp;
public String major;
public String minor;


public static final char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
    char[] hexChars = new char[bytes.length * 2];
    for ( int j = 0; j < bytes.length; j++ ) {
        int v = bytes[j] & 0xFF;
        hexChars[j * 2] = hexArray[v >>> 4];
        hexChars[j * 2 + 1] = hexArray[v & 0x0F];
    }
    return new String(hexChars);
}

public BluetoothAdapter.LeScanCallback mLeScanCallback =
        new BluetoothAdapter.LeScanCallback() {

    @Override
    public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mLeDeviceListAdapter.addDevice(device);
                mLeDeviceListAdapter.notifyDataSetChanged();
            }
        });

        int startByte = 2;
        boolean patternFound = false;
        while (startByte <= 5) {
            if (    ((int) scanRecord[startByte + 2] & 0xff) == 0x02 && //Identifies an iBeacon
                    ((int) scanRecord[startByte + 3] & 0xff) == 0x15) { //Identifies correct data length
                patternFound = true;
                break;
            }
            startByte++;
        }

        if (patternFound) {
            byte[] uuidBytes = new byte[16];
            System.arraycopy(scanRecord, startByte+4, uuidBytes, 0, 16);
            String hexString = bytesToHex(uuidBytes);

                //Here is your UUID
                uuid =  hexString.substring(0,8) + "-" +
                        hexString.substring(8,12) + "-" +
                        hexString.substring(12,16) + "-" +
                        hexString.substring(16,20) + "-" +
                        hexString.substring(20,32);

            major_temp = (scanRecord[startByte+20] & 0xff) * 0x100 + (scanRecord[startByte+21] & 0xff);
            major = Integer.toString(major_temp);
            minor_temp = (scanRecord[startByte+22] & 0xff) * 0x100 + (scanRecord[startByte+23] & 0xff);
            minor = Integer.toString(minor_temp);
          //   TextView textView1 = (TextView) findViewById(R.id.device_uuid);
            // textView1.setText(uuid+" Major: " + major + " Minor: " + minor);


        }

    }

};

static class ViewHolder {
    TextView deviceName;
    TextView deviceAddress;
    TextView deviceUUID;
    TextView deviceMajor;
    TextView deviceMinor;
}

}

And layout file:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="wrap_content">
    <TextView android:id="@+id/device_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="24dp"/>
    <TextView android:id="@+id/device_address"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="12dp"/>
    <TextView android:id="@+id/device_uuid"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="12dp"/>
    <TextView android:id="@+id/device_major"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="12dp"/>
    <TextView android:id="@+id/device_minor"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="12dp"/>
</LinearLayout>
DoeUS
  • 113
  • 13
  • Wow. Your question makes almost no sense. Please post the result you are getting and your desired result. – Andrew G Oct 05 '15 at 19:41
  • Link with the example: http://i.stack.imgur.com/vlepI.jpg I have got two beacons. Names and adresses are correct, but as you can see uuid, major minor are the same and during scanning they keep swapping with each other beacon values. – DoeUS Oct 06 '15 at 12:43

1 Answers1

1

There's a problem in how you handle the beacons found. Just take a careful look at where you assign any value for the UUID, major and minor and what goes to the ListAdapter.

In your LeScanCallback you add any found Bluetooth LE devices into the list adapter. You add them as a BluetoothDevice which is just a generic model and knows nothing about the iBeacon specific UUID, major and minor.

You then exctract the UUID, major and minor into member variables of the class. This is done always when the found BluetoothDevice is recognised as an iBeacon. At any time you are storing just one set of UUID, major and minor. They are always values of the latest iBeacon found.

So instead of having a list of BluetoothDevices you could create a simple class representing an iBeacon with the UUID, major, minor and maybe RSSI and TxPower if you want to do any distance estimation later on. Then in the scan callback do not add BluetoothDevices to the list but add iBeacons at the end of the pattern checking code. Something like:

public BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {

    @Override
    public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
        /* Remove:
        runOnUiThread(new Runnable() {
        @Override
            public void run() {
                mLeDeviceListAdapter.addDevice(device);
                mLeDeviceListAdapter.notifyDataSetChanged();
            }
        });
        */
        int startByte = 2;
        boolean patternFound = false;
        while (startByte <= 5) {
            if (((int) scanRecord[startByte + 2] & 0xff) == 0x02 && //Identifies an iBeacon
                    ((int) scanRecord[startByte + 3] & 0xff) == 0x15) { //Identifies correct data length
                patternFound = true;
                break;
            }
            startByte++;
        }

        if (patternFound) {
            byte[] uuidBytes = new byte[16];
            System.arraycopy(scanRecord, startByte + 4, uuidBytes, 0, 16);
            String hexString = bytesToHex(uuidBytes);

            //Here is your UUID
            uuid = hexString.substring(0, 8) + "-" +
                    hexString.substring(8, 12) + "-" +
                    hexString.substring(12, 16) + "-" +
                    hexString.substring(16, 20) + "-" +
                    hexString.substring(20, 32);

            major_temp = (scanRecord[startByte + 20] & 0xff) * 0x100 + (scanRecord[startByte + 21] & 0xff);
            major = Integer.toString(major_temp);
            minor_temp = (scanRecord[startByte + 22] & 0xff) * 0x100 + (scanRecord[startByte + 23] & 0xff);
            minor = Integer.toString(minor_temp);

            // Add:
            IBeacon iBeacon = new IBeacon(uuid, major, minor);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mLeDeviceListAdapter.addDevice(iBeacon);
                    mLeDeviceListAdapter.notifyDataSetChanged();
                }
            });
        }
    }
}

Then change your list adapter to use iBeacon objects and not BluetoothDevice objects and fix your code in getView(). At the moment you read the UUID, major and minor from the said member variables where they always have the values of the latest iBeacon found.

So instead read the values from the mLeDevices list which now holds iBeacon objects. Something like:

@Override
public View getView(int i, View view, ViewGroup viewGroup) {
    ViewHolder viewHolder;
    if (view == null) {
        view = mInflator.inflate(R.layout.listitem_device, null);
        viewHolder = new ViewHolder();
        viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address);
        viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name);
        viewHolder.deviceUUID = (TextView) view.findViewById(R.id.device_uuid);
        viewHolder.deviceMajor = (TextView) view.findViewById(R.id.device_major);
        viewHolder.deviceMinor = (TextView) view.findViewById(R.id.device_minor);
        view.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) view.getTag();
    }

    BluetoothDevice device = mLeDevices.get(i);
    final String deviceName = device.getName();
    if (deviceName != null && deviceName.length() > 0) {
        viewHolder.deviceName.setText(deviceName);
    }
    else {
        viewHolder.deviceName.setText(R.string.unknown_device);
    }
    viewHolder.deviceAddress.setText(device.getAddress());
    // Change these:
    viewHolder.deviceUUID.setText(device.getUuid);
    viewHolder.deviceMajor.setText(device.getMajor);
    viewHolder.deviceMinor.setText(device.getMinor);

    return view;
}

You would then need to modify your list adapter's addDevice to compare IBeacon objects instead of BluetoothDevice objects.

public void addDevice(IBeacon device) {
    // This probably won't without some extra work:        
    if(!mLeDevices.contains(device)) {
        mLeDevices.add(device);
    }
}
Markus Kauppinen
  • 3,025
  • 4
  • 20
  • 30
  • I did everything as you said in DeviceScanActivity. I created class IBeacon with uuid, major, minor and get methods to receive values in text view. But im getting this on my screen: [Screenshot](http://i.stack.imgur.com/ElmMb.jpg). For two beacons it gives me a lot of positions on my list, beacause every time app calculates uuid etc. it adds me a new beacon device on my list. How can I fix that? – DoeUS Oct 11 '15 at 21:19
  • You'll need to make the addDevice() method to work with IBeacon objects. Maybe the best way to fix this is to override the Object class' equals() and hashCode() methods in the new IBeacon class. Then the contains() and also other comparisons will work. Have a look at [how contains() works](https://stackoverflow.com/questions/2642589/how-does-a-arraylists-contains-method-evaluate-objects) and [overriding equals()](https://stackoverflow.com/questions/27581/what-issues-should-be-considered-when-overriding-equals-and-hashcode-in-java/27609#27609) – Markus Kauppinen Oct 12 '15 at 06:56
  • I override equals() method in my new Ibeacon class and it works great! Thank you very much! – DoeUS Oct 13 '15 at 07:47