/* * Geopaparazzi - Digital field mapping on Android based devices * Copyright (C) 2016 HydroloGIS (www.hydrologis.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package eu.geopaparazzi.library.bluetooth; import java.io.IOException; import java.lang.reflect.Method; import java.util.Collections; import java.util.HashSet; import java.util.Set; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import eu.geopaparazzi.library.database.GPLog; /** * A singleton to manage bluetooth. * * @author Andrea Antonello (www.hydrologis.com) */ public enum BluetoothManager { /** * */ INSTANCE; private BluetoothAdapter _bluetooth; /** * Registered {@link IBluetoothStatusChangeListener status change listeners}. */ private Set<IBluetoothStatusChangeListener> _statusChangeListeners = new HashSet<>(); private BroadcastReceiver _bluetoothState; private BluetoothSocket _bluetoothSocket; private BluetoothDevice _bluetoothDevice; private boolean isSocketConnected = false; private boolean isDummy = false; private IBluetoothIOHandler iBluetoothDevice; private BluetoothManager() { _bluetooth = BluetoothAdapter.getDefaultAdapter(); } /** * Create dummy instance. */ public void makeDummy() { try { reset(); } catch (Exception e) { GPLog.error(this, null, e); } // needs to be set afterwards, since reset puts it to false isDummy = true; } /** * Checks if bt is supported. * * <p>Might not be available on certain devices.</p> * * @return <code>true</code> if the device is supported. */ public boolean isSupported() { if (isDummy) return true; return _bluetooth != null; } /** * Checks if the bt device is turned on. * * @return <code>true</code> if the bt device is truned on. */ public boolean isEnabled() { if (isDummy) return true; boolean supported = isSupported(); boolean enabled = _bluetooth.isEnabled(); if (supported && enabled) { return true; } else { return false; } } /** * @return the device's hardware address. */ public String getAddress() { if (isDummy) return "dummyaddress"; //$NON-NLS-1$ if (isEnabled()) { return _bluetooth.getAddress(); } else { return null; } } /** * @return the device's userfriendly name. */ public String getName() { if (isDummy) return "dummy device"; //$NON-NLS-1$ if (isEnabled()) { return _bluetooth.getName(); } else { return null; } } /** * Polls te state of the bt device. * * @return the state of the bt device. */ public int getState() { if (isDummy) return BluetoothAdapter.STATE_ON; if (isEnabled()) { return _bluetooth.getState(); } else { return BluetoothAdapter.STATE_OFF; } } /** * Enable the bluetooth adapter. * * <p> * This launches an activity to do so and returns when the bt device has * been swiced on. * </p> * <p> * The parentActivity will have to check the onActivityResult method like: * <pre> * if(requestCode == myCode){ * if(resultCode == RESULT_OK){ * // BT enabled * } * } * </pre> * </p> * * @param parentActivity the {@link Activity} to use for the bt activity to start. * @param requestCode the request code. */ public void enable( Activity parentActivity, int requestCode ) { parentActivity.startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), requestCode); } /** * Adds a status change listener. * * @param listener the {@link IBluetoothStatusChangeListener listener} to add. */ public void addStatusChangedListener( IBluetoothStatusChangeListener listener ) { _statusChangeListeners.add(listener); } /** * Removes a status change listener. * * @param listener the {@link IBluetoothStatusChangeListener listener} to remove. */ public void removeStatusChangedListener( IBluetoothStatusChangeListener listener ) { _statusChangeListeners.remove(listener); } /** * Starts to listen to status changes. * * @param context the {@link Context} to use. */ public void startStatusChangeListening( Context context ) { _bluetoothState = new BroadcastReceiver(){ @Override public void onReceive( Context context, Intent intent ) { String prevStateExtra = BluetoothAdapter.EXTRA_PREVIOUS_STATE; String stateExtra = BluetoothAdapter.EXTRA_STATE; int state = intent.getIntExtra(stateExtra, -1); int previousState = intent.getIntExtra(prevStateExtra, -1); if (state != previousState) { for( IBluetoothStatusChangeListener listener : _statusChangeListeners ) { listener.bluetoothStatusChanged(previousState, state); } } String tt = ""; //$NON-NLS-1$ switch( state ) { case BluetoothAdapter.STATE_TURNING_ON: tt = "Bluetooth turning on..."; //$NON-NLS-1$ break; case BluetoothAdapter.STATE_ON: tt = "Bluetooth on..."; //$NON-NLS-1$ break; case BluetoothAdapter.STATE_TURNING_OFF: tt = "Bluetooth turning off..."; //$NON-NLS-1$ break; case BluetoothAdapter.STATE_OFF: tt = "Bluetooth off..."; //$NON-NLS-1$ break; default: break; } if (GPLog.LOG) GPLog.addLogEntry(this, null, null, tt); } }; context.registerReceiver(_bluetoothState, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); } /** * Stop listening to status changes. * * @param context the {@link Context} to use. */ public void stopStatusChangeListening( Context context ) { if (_bluetoothState != null) { context.unregisterReceiver(_bluetoothState); } } /** * Get the set of paired devices. * * @return the set of paired devices or an empty set. */ public Set<BluetoothDevice> getBondedDevices() { if (isEnabled()) { return _bluetooth.getBondedDevices(); } else { return Collections.emptySet(); } } /** * Get a {@link BluetoothDevice} by its address. * * @param address te bt device address. * @return the device or <code>null</code>. */ public BluetoothDevice getBluetoothDeviceByAddress( String address ) { if (isEnabled()) { return _bluetooth.getRemoteDevice(address); } else { return null; } } /** * Get a {@link BluetoothDevice} by its name. * * @param name te bt device name. * @return the device or <code>null</code>. */ public BluetoothDevice getBluetoothDeviceByName( String name ) { if (isEnabled()) { Set<BluetoothDevice> bondedDevices = getBondedDevices(); for( BluetoothDevice bluetoothDevice : bondedDevices ) { if (bluetoothDevice.getName().equals(name)) { return bluetoothDevice; } } return null; } else { return null; } } /** * Set the {@link BluetoothDevice}. * * <p>If another one is available, its socket will be closed and * a new connection is made with the new device.</p> * * @param bluetoothDevice the device to use. * @param connect if <code>true</code>, also connect to the socket. * @throws Exception if something goes wrong. */ public synchronized void setBluetoothDevice( BluetoothDevice bluetoothDevice, boolean connect ) throws Exception { reset(); _bluetoothDevice = bluetoothDevice; // reset if (iBluetoothDevice != null) { iBluetoothDevice.close(); iBluetoothDevice = null; } if (connect) { getSocket(); } } /** * Reset the current bluetooth socket and device. * * @throws Exception if something goes wrong. */ public synchronized void reset() throws Exception { if (_bluetoothSocket != null) { _bluetoothSocket.close(); isSocketConnected = false; _bluetoothSocket = null; } _bluetoothDevice = null; isDummy = false; } /** * Get the bt socket. * * <p>The socket is defined when the device is chosen.</p> * * @return the active bt socket or <code>null</code>. * @throws Exception if something goes wrong. */ public synchronized BluetoothSocket getSocket() throws Exception { if (isDummy) return null; if (_bluetoothSocket == null) { if (isEnabled()) { createSocket(); } else { throw new RuntimeException(); } } return _bluetoothSocket; } /** * @return the current bluetooth device. */ public synchronized BluetoothDevice getCurrentBluetoothDevice() { return _bluetoothDevice; } /** * Create a bluetooth (rfcomm) socket and connect to it. * * @throws Exception */ private void createSocket() throws Exception { Method m = _bluetoothDevice.getClass().getMethod("createRfcommSocket", new Class[]{int.class}); //$NON-NLS-1$ _bluetoothSocket = (BluetoothSocket) m.invoke(_bluetoothDevice, 1); _bluetoothSocket.connect(); isSocketConnected = true; } /** * @return <code>true</code>, if the device is ready to transfer data through the socket. */ public boolean isIOReady() { if (isDummy) return true; return isSocketConnected; } /** * Initializes * * @param iBluetoothDevice the bt handler. * @throws IOException if something goes wrong. */ public void initializeIBluetoothDeviceInternal( IBluetoothIOHandler iBluetoothDevice ) throws IOException { this.iBluetoothDevice = iBluetoothDevice; if (isDummy) return; if (isSocketConnected) { iBluetoothDevice.initialize(_bluetoothSocket); } else { throw new IOException("No socket connected."); //$NON-NLS-1$ } } /** * @return the bt device. */ public IBluetoothIOHandler getBluetoothDevice() { return iBluetoothDevice; } }