package eu.hgross.blaubot.android.bluetooth; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.UUID; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.ParcelUuid; import android.os.Parcelable; import eu.hgross.blaubot.util.Log; /** * Decoupled receiver object to find devices via the bluetooth adapter's discovery method. * * @author Henning Gross {@literal (mail.to@henning-gross.de)} * */ public class BlaubotBluetoothDeviceDiscoveryReceiver extends BroadcastReceiver { private List<BluetoothDevice> discoveredBluetoothDevices; private HashMap<BluetoothDevice, List<UUID>> deviceServicesMapping; private static final String DEBUG_TAG = "DeviceDiscoveryReceiver"; private static final long KEEP_PERIOD = 12000; // ms private final Object monitor = new Object(); private List<IBluetoothDiscoveryListener> bluetoothDiscoveryListeners; private long lastDiscoveryTimestamp = 0; private boolean fetchUUIDs; public BlaubotBluetoothDeviceDiscoveryReceiver(boolean fetchUUIDsAutomatically) { this.fetchUUIDs = fetchUUIDsAutomatically; this.discoveredBluetoothDevices = Collections.synchronizedList(new ArrayList<BluetoothDevice>()); this.bluetoothDiscoveryListeners = Collections.synchronizedList(new ArrayList<IBluetoothDiscoveryListener>()); this.deviceServicesMapping = new HashMap<BluetoothDevice, List<UUID>>(); } private int start_finished_counter = 0; public boolean isDone() { return start_finished_counter == 0; } public void addBluetoothDiscoveryListener(IBluetoothDiscoveryListener bluetoothDiscoveryListener) { this.bluetoothDiscoveryListeners.add(bluetoothDiscoveryListener); } public void removeBluetoothDiscoveryListener(IBluetoothDiscoveryListener bluetoothDiscoveryListener) { this.bluetoothDiscoveryListeners.remove(bluetoothDiscoveryListener); } @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); Log.d(DEBUG_TAG, "Discovered device: " + device.getName() + ", " + device); synchronized (monitor) { if(!discoveredBluetoothDevices.contains(device)) { discoveredBluetoothDevices.add(device); } } notifyDiscovery(); } else if (BluetoothDevice.ACTION_UUID.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); Parcelable[] uuidExtra = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID); List<UUID> services = null; synchronized (monitor) { services = deviceServicesMapping.get(device); if (services == null) { services = new ArrayList<UUID>(); deviceServicesMapping.put(device, services); } } if (uuidExtra != null) { for (int i = 0; i < uuidExtra.length; i++) { UUID service = ((ParcelUuid)uuidExtra[i]).getUuid(); // Log.d(DEBUG_TAG, "Discovered service of: " + device.getName() + ", " + device + ", Service: " + service.toString()); synchronized (monitor) { if (!services.contains(service)) { services.add(service); } } } notifyDiscovery(); } } else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) { Log.d(DEBUG_TAG, "\nDiscovery Started..."); synchronized (monitor) { start_finished_counter += 1; } if (start_finished_counter == 1) { // only clear if the last discovery is a while ago (to overcome the retrigger behaviour) if (System.currentTimeMillis() - lastDiscoveryTimestamp > KEEP_PERIOD) { discoveredBluetoothDevices.clear(); deviceServicesMapping.clear(); } lastDiscoveryTimestamp = System.currentTimeMillis(); notifyStarted(); } } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { Log.d(DEBUG_TAG, "\nDiscovery Finished"); Iterator<BluetoothDevice> itr = discoveredBluetoothDevices.iterator(); while (fetchUUIDs && itr.hasNext()) { // Get Services for paired devices BluetoothDevice device = itr.next(); Log.d(DEBUG_TAG, "\nGetting Services for " + device.getName() + ", " + device); // device.getUuids(); if (!device.fetchUuidsWithSdp()) { Log.d("BlueBot", "\nSDP Failed for " + device.getName()); } } synchronized (monitor) { // Some android devices trigger DISCOVERY_STARTED more often than DISCOVERY_FINISHED so we // fix this with this condition. if(!BluetoothAdapter.getDefaultAdapter().isDiscovering()) { start_finished_counter = 0; } else { start_finished_counter -= 1; } } if (isDone()) { notifyFinished(); } } } private void notifyStarted() { // Log.d(DEBUG_TAG, "Notifying: Discovery started"); for (IBluetoothDiscoveryListener listener : bluetoothDiscoveryListeners) { listener.onDiscoveryStarted(); } } private void notifyDiscovery() { // Log.d(DEBUG_TAG, "Notifying: List of discovered devices/services changed."); // copy data structures ArrayList<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(this.discoveredBluetoothDevices); HashMap<BluetoothDevice, List<UUID>> serviceMapping = new HashMap<BluetoothDevice, List<UUID>>(this.deviceServicesMapping); for (BluetoothDevice device : serviceMapping.keySet()) { serviceMapping.put(device, new ArrayList<UUID>(serviceMapping.get(device))); } for (IBluetoothDiscoveryListener listener : bluetoothDiscoveryListeners) { listener.onDiscovery(devices, serviceMapping); } } private void notifyFinished() { // Log.d(DEBUG_TAG, "Notifying: Discovery finished"); // copy data structures ArrayList<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(this.discoveredBluetoothDevices); HashMap<BluetoothDevice, List<UUID>> serviceMapping = new HashMap<BluetoothDevice, List<UUID>>(this.deviceServicesMapping); for (BluetoothDevice device : serviceMapping.keySet()) { serviceMapping.put(device, new ArrayList<UUID>(serviceMapping.get(device))); } for (IBluetoothDiscoveryListener listener : bluetoothDiscoveryListeners) { listener.onDiscoveryFinished(devices, serviceMapping); } } public List<BluetoothDevice> getDiscoveredBluetoothDevices() { return discoveredBluetoothDevices; } /** * The device object for the given macAddress, if the device was already discovered. * * @param macAddress the macAddress to search for * @return The device object for the given macAddress, if the device was already discovered - null otherwise */ public BluetoothDevice getBluetoothDeviceByAddress(String macAddress) { for(BluetoothDevice b : this.discoveredBluetoothDevices) { if (b.getAddress().equals(macAddress)) return b; } return null; } /** * Creates an {@link IntentFilter} for the bluetooth intents needed by this {@link BroadcastReceiver}. * @return the set up {@link IntentFilter} */ public static IntentFilter createBluetoothIntentFilter() { // register bluetooth related intents IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothDevice.ACTION_FOUND); filter.addAction(BluetoothDevice.ACTION_UUID); filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); return filter; } }