I am using a AIDL system service which interacts with serial port of HW component.
My goal is on 1 method to bind to AIDL service and then connect to the HW on same method (this is a constraint).
For test, from Activity, if I bind on onCreate and use serialOpen on button event, it works (I can connect to the HW).
2021-12-09 01:15:54.322 11904-11904/com.example.hwtestapp D/RFID: Binding to serial service..
2021-12-09 01:15:54.559 11904-11904/com.example.hwtestapp D/RFID: Service connected.
If I remove the service binding from onCreate and use only serialOpen (which take care of binding), it fails! First click with binding error, the second time, I can connect (because it has been binded on first click). I do not explain the reason of that.
First click, it waits SERVICE_BINDING_TIMEOUT and raise an error. As soon it exits from the method, it connects to the service.
I tried to increase the SERVICE_BINDING_TIMEOUT behaviour is the same, it just take more delay.
2021-12-09 01:11:38.513 10927-10927/com.example.hwtestapp D/example: Binding to serial service..
2021-12-09 01:11:39.027 10927-10927/com.example.hwtestapp E/example: Error binding to serial service
2021-12-09 01:11:39.039 10927-10927/com.example.hwtestapp I/Choreographer: Skipped 32 frames! The application may be doing too much work on its main thread.
2021-12-09 01:11:39.063 10927-10927/com.example.hwtestapp D/example: Service connected.
I tried to bind on thread, asyncTask, no effect. Bind looks to wait the end of method.
it looks like to complete the binding, operating system needs to have the hand.
Any idea is welcome.
THanks!
public void serialOpen(Context context) throws IOException {
if (context == null) throw new InvalidParameterException("Context cannot be Null");
this.context = context;
// bind if not already done
if(!bound) bindToSerialService(context, true);
// wait for binding completion
long t = SystemClock.elapsedRealtime();
while(!bound && (SystemClock.elapsedRealtime() - t < SERVICE_BINDING_TIMEOUT)){
SystemClock.sleep(10);
}
if(!bound) throw new IOException("Error binding to serial service");
String[] ports;
try {
// get port names when bind has completed
ports = serial.getPortNames();
if (ports.length == 0) throw new IOException("No serial port detected");
for (String p : ports) if(DEBUG) Log.d(TAG, "Port list contains: " + p);
port = serial.open(ports[0]);
if(DEBUG) Log.d(TAG, "Open returned " + port);
if (port == null) throw new IOException("Error opening serial port");
boolean ok = port.configure(baudRate, DATA_BITS, PARITY, STOP_BITS, FLOW_CONTROL);
if(DEBUG) Log.d(TAG, "Configuration returned " + ok);
//...
isConnected = true;
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void bindToSerialService(Context context, boolean state) {
if (context == null) throw new InvalidParameterException("Context cannot be Null");
this.context = context;
if(state) {
// bind to service
if(DEBUG) Log.d(TAG, "Binding to serial service..");
Intent intent = new Intent().setComponent(new ComponentName("com.example.serial", "com.example.serial.SerialService"));
context.bindService(intent, serialConnection, Context.BIND_AUTO_CREATE);
} else {
context.unbindService(serialConnection);
bound = false;
if(DEBUG) Log.d(TAG, "Unbound to serial service.");
}
}
Refer to Android how do I wait until a service is actually connected?
Android 10 has introduced a new bindService method signature when binding to a service to provide an Executor (which can be created from the Executors). It helps to fix my issue
/**
* Same as {#link #bindService(Intent, ServiceConnection, int)} with executor to control
* ServiceConnection callbacks.
* #param executor Callbacks on ServiceConnection will be called on executor. Must use same
* instance for the same instance of ServiceConnection.
*/
public boolean bindService(#RequiresPermission #NonNull Intent service,
#BindServiceFlags int flags, #NonNull #CallbackExecutor Executor executor,
#NonNull ServiceConnection conn) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
This allows to bind to the service in a thread and wait until it is connected.
private final AtomicBoolean connected = new AtomicBoolean()
private final Object lock = new Object();
...
/**
* Monitors the connection to the serial service.
*/
private final ServiceConnection serialConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
synchronized (lock) {
serial = ISerial.Stub.asInterface(iBinder);
if (DEBUG) Log.d(TAG, "Service connected.");
bound.set(true);
lock.notifyAll();
}
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
serial = null;
if(DEBUG) Log.d(TAG, "Service disconnected.");
bound.set(false);
}
};
/**
* binds to serial system service
* #param context context
*/
public void bindToSerialService(Context context) throws IOException {
if (context == null) throw new InvalidParameterException("Context cannot be Null");
this.context = context;
// bind to service
if (DEBUG) Log.d(TAG, "Binding to serial service..");
ExecutorService executorService = Executors.newSingleThreadExecutor();
Intent intent = new Intent().setComponent(new ComponentName("com.example.serial", "com.example.serial.SerialService"));
context.bindService(intent, Context.BIND_AUTO_CREATE, executorService, serialConnection);
// wait the connection completion to the serial service
synchronized (lock) {
try {
if (DEBUG) Log.d(TAG, "Waiting the binding..");
lock.wait(SERVICE_BINDING_TIMEOUT);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
if (!bound.get()) {
context.unbindService(serialConnection);
throw new IOException("Error binding to serial service");
}
}
}
/**
* unbinds to serial system service
* #param context context
*/
public void unbindFromSerialService() {
if(bound.get()) {
context.unbindService(serialConnection);
bound.set(false);
if(DEBUG) Log.d(TAG, "Unbound to serial service.");
} else {
if(DEBUG) Log.d(TAG, "Not bound or already unbound to serial service.");
}
}
public void serialOpen(Context context) throws IOException {
if (context == null) throw new InvalidParameterException("Context cannot be Null");
this.context = context;
// bind to serial service if not already done
if(!bound.get()) bindToSerialService(context);
try {
// get port names when bind has completed
String[] ports = serial.getPortNames();
if (ports.length == 0) throw new IOException("No serial port detected");
for (String p : ports) if(DEBUG) Log.d(TAG, "Port list contains: " + p);
port = serial.open(ports[0]);
if(DEBUG) Log.d(TAG, "Open returned " + port);
if (port == null) throw new IOException("Error opening serial port");
boolean ok = port.configure(baudRate, DATA_BITS, PARITY, STOP_BITS, FLOW_CONTROL);
if(DEBUG) Log.d(TAG, "Configuration returned " + ok);
//...
isConnected = true;
} catch (RemoteException e) {
e.printStackTrace();
}
}
Related
I have an android app to connect to a BLE device and write to it. I can successfully connect, read and write to it. As a part of testing, we are trying different disconnection scenarios.
Sometimes, if BLE device disconnects the connection, I get the connection change as disconnect with status value as 19. Also if there is any bond error, status equals 22. If I programmatically disconnect the connection, this status gives me 0. But none of these states except 0 are specified in android documentation.
Posting a sample BluetoothGattCallback
private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
Log.i(TAG, "onConnectionStateChange status: "+status+", newState: "+newState);
/*i need to know the possible values for this status variable*/
if(newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices();
} else {
gatt.close();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.i(TAG, "onServicesDiscovered service discovered");
}
};
Does anyone face this same problem and sorted out the list of statuses. I need to know the possible values for status variable in onConnectionStateChange method
Here is the list of codes i have
Programmatically disconnected - 0
Device went out of range - 8
Disconnected by device - 19
Issue with bond - 22
Device not found - 133(some phone it gives 62)
I have tested disconnect scenario's in 5.0.2, 5.1, 6.0 and 6.0.1. But only found this bond issue code in 6.0.1 android version.
Sorry to bring up an old question, but here is the solution for many of the problems i've had with Bluetooth (BLE) 4.0. Sorry again for the big classes below but be sure they are needed and no method there is irrelevant or unused.
public abstract class AbstractBluetoothBroadcaster extends BroadcastReceiver {
protected static final String LOG_TAG = BluetoothLowEnergy.LOG_TAG;
protected BluetoothLowEnergy bluetoothLowEnergy;
public AbstractBluetoothBroadcaster(BluetoothLowEnergy bluetoothLowEnergy, String action){
super();
this.bluetoothLowEnergy = bluetoothLowEnergy;
IntentFilter intentFilterStateChange = new IntentFilter(action);
intentFilterStateChange.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
this.bluetoothLowEnergy.getActivity().registerReceiver(this, intentFilterStateChange);
}
public void onDestroy(){
this.bluetoothLowEnergy.getActivity().unregisterReceiver(this);
}
}
public class BluetoothBondStateBroadcaster extends AbstractBluetoothBroadcaster {
private BluetoothLowEnergy bluetoothLowEnergy;
private boolean deviceBonded;
public BluetoothBondStateBroadcaster(BluetoothLowEnergy bluetoothLowEnergy) {
super(bluetoothLowEnergy, BluetoothDevice.ACTION_BOND_STATE_CHANGED);
this.bluetoothLowEnergy = bluetoothLowEnergy;
this.deviceBonded = false;
}
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null){
return;
}
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED) &&
bluetoothDevice != null &&
bluetoothDevice.getAddress().equals(bluetoothLowEnergy.getDeviceUUID())) {
int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
switch (state) {
case BluetoothDevice.BOND_NONE:
Log.d(LOG_TAG, " NOT BONDED - dev " + bluetoothDevice.getAddress());
this.deviceBonded = false;
break;
case BluetoothDevice.BOND_BONDING:
Log.d(LOG_TAG, " BONDING ... - dev " + bluetoothDevice.getAddress());
break;
case BluetoothDevice.BOND_BONDED:
Log.d(LOG_TAG, " BONDED - dev " + bluetoothDevice.getAddress());
deviceBonded = true;
bluetoothLowEnergy.onBluetoothBonded();
break;
default:
break;
}
}
}
public void resetDeviceBonded(){
this.deviceBonded = false;
}
public boolean isDeviceBonded() {
return deviceBonded;
}
}
public class BluetoothPairingBroadcaster extends AbstractBluetoothBroadcaster {
private String devicePIN;
public BluetoothPairingBroadcaster(BluetoothLowEnergy bluetoothLowEnergy){
super(bluetoothLowEnergy, BluetoothDevice.ACTION_PAIRING_REQUEST);
this.devicePIN = "";
}
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null){
return;
}
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int pairingType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST) &&
bluetoothDevice != null &&
bluetoothDevice.getAddress().equals(bluetoothLowEnergy.getDeviceUUID()) &&
!getDevicePIN().isEmpty()) {
if (pairingType == BluetoothDevice.PAIRING_VARIANT_PIN){
bluetoothDevice.setPin(getDevicePIN().getBytes());
Log.d(LOG_TAG," Auto-entering pin - " + getDevicePIN());
bluetoothDevice.createBond();
Log.d(LOG_TAG," pin entered and request sent...");
abortBroadcast();
}
}
}
public void setDevicePIN(String pin){
this.devicePIN = pin;
}
public String getDevicePIN(){
return this.devicePIN ;
}
}
public class BluetoothLowEnergy extends BluetoothGattCallback {
// listener that has the methods that the application (activity)
// will use to send / receive data, or to reflect the system state
// in the UI
public interface BluetoothListener {
/**
* Triggered when the scanning has started successfully
*/
void onBluetoothStartScan();
/**
* Triggered when the scanning stops
* #param scanResults results of the scanning
*/
void onBluetoothStopScan(Collection<BluetoothDevice> scanResults);
/**
* Triggered when the device is ready to send/receive data
*/
void onBluetoothConnectionReady();
/**
* Triggered when a bluetooth msg is received
* #param msg message received
*/
void onBluetoothReceiveMsg(String msg);
/**
* Triggered whenever data is send
* #param success true means data was sent fine to the remote device, false otherwise
*/
void onBluetoothSend(String data, boolean success);
/**
* Triggered if no bluetooth is connected, and we need a connection
* to send / receive / discover services
*/
void onBluetoothNotConnected();
}
// custom exceptions
public class BluetoothNotEnabledException extends Exception { }
public class BluetoothLowEnergyNotSupported extends Exception { }
public class BluetoothDeviceNotFound extends Exception { }
// service and characteristic uuids that are going to be used to
// send / receive data between central and peripheral GATTs
private static final String SERVICE_UUID = "FFE0-";
private static final String CHARACTERISTIC_UUID = "FFE1-";
// timeout for bluetooth scan (in ms)
public static final int SCAN_TIMEOUT = 5000;
// BLE LOG TAG
public static final String LOG_TAG = "BLUETOOTH_BLE";
// model
private boolean bluetoothScanning;
private boolean bluetoothConnected;
private Map<String, BluetoothDevice> bluetoothScanResults;
// gui
private Activity activity;
// bluetooth
private BluetoothAdapter bluetoothAdapter;
private BluetoothLeScanner bluetoothLeScanner;
private ScanCallback bluetoothScanCallback;
private BluetoothGatt bluetoothGatt;
private BluetoothGattCharacteristic characteristic;
public BluetoothLowEnergy(Activity activity, BluetoothListener bluetoothListener){
this.activity = activity;
this.bluetoothListener = bluetoothListener;
// this keeps track of the scanning and connection states
this.bluetoothScanning = this.bluetoothConnected = false;
// keeps track of the scanning results
this.bluetoothScanResults = new HashMap<>();
// set bluetooth pairing request and bonded callback
// these broadcasters will be responsible to detect and validate
// the bonded state of your device
this.pairingRequestBroadcaster = new BluetoothPairingBroadcaster(this);
this.bondedBroadcaster = new BluetoothBondStateBroadcaster(this);
// set the scan callback methods that will add results to
// this.bluetoothScanResults map
this.bluetoothScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
addScanResult(result);
}
#Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
for (ScanResult result: results) {
addScanResult(result);
}
}
#Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(LOG_TAG, "Scan Failed with code " + errorCode);
}
private void addScanResult(ScanResult result) {
BluetoothDevice device = result.getDevice();
String deviceAddress = device.getAddress();
bluetoothScanResults.put(deviceAddress, device);
Log.d(LOG_TAG, "Found device " + deviceAddress);
}
};
// Use this to determine whether BLE is supported on the device.
if (!this.activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
throw new BluetoothLowEnergyNotSupported();
}
}
/**
* This method should be called when the activity is destroyed
*/
public void onDestroy(){
this.bondedBroadcaster.onDestroy();
this.pairingRequestBroadcaster.onDestroy();
this.disconnect();
}
/**
* This method is called when we finish pairing/bonding to the device
*/
public void onBluetoothBonded(){
// if we have the services already discovered, then we can
// send/receive data, to do so we call the bluetooth listener below
if (servicesDiscovered){
this.bluetoothListener.onBluetoothConnectionReady();
// if we know we have a connection established, then we can
// discover services
} else if (bluetoothConnected){
bluetoothGatt.discoverServices();
}
}
/**
* This method is called whenever a connection is established or a disconnection happens
*/
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
BluetoothDevice bluetoothDevice = gatt.getDevice();
// if these conditions == true, then we have a disconnect
if ( status == BluetoothGatt.GATT_FAILURE ||
status != BluetoothGatt.GATT_SUCCESS ||
newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Disconnected from %s (%s) - status %d - state %d",
bluetoothDevice.getName(),
bluetoothDevice.getAddress(),
status,
newState
));
this.disconnect();
// if these conditions == true, then we have a successful connection
} else if (newState == BluetoothProfile.STATE_CONNECTED) {
bluetoothConnected = true;
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Connected to %s (%s) - status %d - state %d",
bluetoothDevice.getName(),
bluetoothDevice.getAddress(),
status,
newState
));
// this sleep is here to avoid TONS of problems in BLE, that occur whenever we start
// service discovery immediately after the connection is established
try {
Thread.sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}
gatt.discoverServices();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status != BluetoothGatt.GATT_SUCCESS) {
return;
}
// BEGIN - find the service and characteristic that we want (defined as a static attribute
// of the BluetoothLowEnergy class)
Log.d(LOG_TAG, "Discovering services ...");
BluetoothGattService service = null;
for (BluetoothGattService serv: gatt.getServices()){
Log.d(LOG_TAG, "Found service " + serv.getUuid().toString());
if (serv.getUuid().toString().toUpperCase().contains(SERVICE_UUID)){
service = serv;
Log.d(LOG_TAG, "---> Selected service " + serv.getUuid().toString());
break;
}
}
if (service == null){
return;
}
for (BluetoothGattCharacteristic charac: service.getCharacteristics()){
Log.d(LOG_TAG, "Found characteristic " + charac.getUuid().toString());
if (charac.getUuid().toString().toUpperCase().contains(CHARACTERISTIC_UUID)){
this.characteristic = charac;
Log.d(LOG_TAG, "---> Selected characteristic " + charac.getUuid().toString());
break;
}
}
if (this.characteristic == null){
return;
}
Log.d(LOG_TAG, "Setting write and notification to the characteristic ...");
bluetoothAdapter.cancelDiscovery();
// END - find the service and characteristic
// set that we want to write to the selected characteristic and be notified if
// it changes (the remote GATT peripheral sends data to the Android's GATT Center)
this.characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
gatt.setCharacteristicNotification(this.characteristic, true);
// we finished service discovery
this.servicesDiscovered = true;
// if we have paired/bonded then we are ready to send/receive data
if (pairingRequestBroadcaster.getDevicePIN().isEmpty() || bondedBroadcaster.isDeviceBonded()) {
this.bluetoothListener.onBluetoothConnectionReady();
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic charac, int status) {
super.onCharacteristicRead(gatt, charac, status);
restartDisconnectTimeout();
if (status != BluetoothGatt.GATT_SUCCESS) {
return;
}
try {
String characValue = new String(charac.getValue(), CHARSET)
.replaceAll(DATA_FILTER_REGEX,"");
Log.i(LOG_TAG, String.format(Locale.getDefault(),
"Characteristic Read - %s",
characValue
));
if (charac.getUuid().equals(this.characteristic.getUuid())) {
this.bluetoothListener.onBluetoothReceiveMsg(characValue);
}
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Characteristic Read - Failed to convert message string to byte array");
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic charac, int status) {
super.onCharacteristicWrite(gatt, charac, status);
restartDisconnectTimeout();
try {
String characValue = new String(charac.getValue(), CHARSET);
Log.i(LOG_TAG, String.format(Locale.getDefault(),
"Characteristic Write - SUCCESS - %s",
characValue
));
bluetoothListener.onBluetoothSend( characValue, (status == BluetoothGatt.GATT_SUCCESS) );
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Characteristic Write - Failed to convert message string to byte array");
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic charac) {
super.onCharacteristicChanged(gatt, charac);
Log.d(LOG_TAG,"Characteristic Changed");
onCharacteristicRead(gatt, charac, BluetoothGatt.GATT_SUCCESS);
}
/**
* Remove pairing/bonding of the device
* #param device Device to remove bonding
*/
public static void removeBond(BluetoothDevice device){
try {
if (device == null){
throw new Exception();
}
Method method = device.getClass().getMethod("removeBond", (Class[]) null);
method.invoke(device, (Object[]) null);
Log.d(LOG_TAG, "removeBond() called");
Thread.sleep(600);
Log.d(LOG_TAG, "removeBond() - finished method");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Clears the GATT services cache, so that new services can be discovered
* #param bluetoothGatt GATT Client to clear service's discovery cache
*/
public static void refresh(BluetoothGatt bluetoothGatt){
try {
Method method = bluetoothGatt.getClass().getMethod("refresh", (Class[]) null);
method.invoke(bluetoothGatt, (Object[]) null);
} catch (Exception e){
e.printStackTrace();
}
}
/**
* Connect to the GATT Peripheral device
* #param uuid GATT Peripheral address / mac / uuid to connect to
* #param pin PIN to authenticate and pair to the device
*/
public void connect(String uuid, String pin) throws BluetoothNotEnabledException, BluetoothDeviceNotFound {
checkBluetooth();
// do not connect twice
if (this.isConnected()){
return;
}
// get device
BluetoothDevice device = this.bluetoothScanResults.get(uuid);
if (device == null){
throw new BluetoothDeviceNotFound();
}
this.deviceUUID = uuid;
pairingRequestBroadcaster.setDevicePIN(pin);
removeBond(device);
// create connection to the bluetooth device
bluetoothGatt = device.connectGatt(activity, false, this);
refresh(bluetoothGatt);
}
/**
* Disconnect from BLE device. This method should be called whenever we want to
* close the APP, or the BLE connection.
*/
public void disconnect() {
Log.d(LOG_TAG, "disconnect() - executed");
if (bluetoothGatt != null) {
if (characteristic != null) {
bluetoothGatt.setCharacteristicNotification(characteristic, false);
}
//remove device authorization/ bond/ pairing
removeBond(bluetoothGatt);
// disconnect now
bluetoothGatt.disconnect();
bluetoothGatt.close();
Log.d(LOG_TAG, "disconnect() - bluetoothGatt disconnect happened");
}
bluetoothGatt = null;
characteristic = null;
bluetoothConnected = false;
servicesDiscovered = false;
// set device as not bonded anymore
bondedBroadcaster.resetDeviceBonded();
}
/**
* bluetooth nearby devices scan is on
* #return true if scanning is on, false otherwise
*/
public boolean isScanning(){
return (this.bluetoothScanning);
}
/**
* Check bluetooth system state (on or off)
* #return true if system is on, false otherwise
*/
public boolean isEnabled(){
try {
checkBluetooth();
return bluetoothAdapter.isEnabled();
} catch (BluetoothNotEnabledException e) {
return false;
}
}
/**
* Check bluetooth connection
* #return true if connected, false otherwise
*/
public boolean isConnected(){
return (this.bluetoothConnected);
}
/**
* Start bluetooth scan for nearby devices
* #param filters Scan filters that define what devices to scan for
*/
public void startScan(List<ScanFilter> filters)
throws BluetoothNotEnabledException{
checkBluetooth();
// dont run two scans simultaneously
if (isScanning()) {
return;
}
// disconnect previously connected devices
if (isConnected()) {
this.disconnect();
return;
}
// setup bluetooth scanning settings
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.build();
// start scanning
this.bluetoothScanning = true;
this.bluetoothScanResults.clear();
this.bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
// Stops scanning after a pre-defined scan period.
Handler bluetoothHandler = new Handler();
bluetoothHandler.postDelayed(new Runnable() {
#Override
public void run() {
stopScan();
}
}, SCAN_TIMEOUT);
// start scan with default scan callback
this.bluetoothLeScanner.startScan(filters, settings, bluetoothScanCallback);
// we have started successfully the BLE scanning
bluetoothListener.onBluetoothStartScan();
}
/**
* Stop bluetooth scan for nearby devices
*/
public void stopScan(){
if (!bluetoothScanning) {
return;
}
// set app scan state to false
bluetoothScanning = false;
if (bluetoothLeScanner != null) {
bluetoothLeScanner.stopScan(bluetoothScanCallback);
bluetoothLeScanner = null;
}
// we have stopped BLE scanning, call the user's callback
bluetoothListener.onBluetoothStopScan(bluetoothScanResults.values());
}
/**
* Send a message via bluetooth
* #param msg message to send
*/
public void send(String msg) {
if (!bluetoothConnected || characteristic == null){
bluetoothListener.onBluetoothNotConnected();
return;
}
try {
msg = msg.replaceAll(DATA_FILTER_REGEX, "") + TERMINATION_CHAR;
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Sending message: %s",
msg));
characteristic.setValue(msg.getBytes(CHARSET));
bluetoothGatt.writeCharacteristic(characteristic);
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG,
"BluetoothLowEnergy.send: Failed to convert message string to byte array");
}
}
public String getDeviceUUID(){
return deviceUUID;
}
public Activity getActivity(){
return activity;
}
/**
* Check if bluetooth is enabled and working properly
*/
private void checkBluetooth() throws BluetoothNotEnabledException{
if (bluetoothAdapter == null) {
final BluetoothManager bluetoothManager =
(BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager == null){
throw new BluetoothNotEnabledException();
}
bluetoothAdapter = bluetoothManager.getAdapter();
}
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
throw new BluetoothNotEnabledException();
}
}
}
The key methods and functions to avoid problems used above are:
Thread.sleep(600)
removeBond(device)
refresh(gatt)
gatt.disconnect()
gatt.close()
In my case I got this response from bluetooth stack because the device was already bonded with my phone. I removed it from my settings and the error 22 vanished.
in aosp (android source code). you can find any error in bluetooth source code, and know the meaning of status code.
the file path is system/bt/stack/include/gatt_api.h
Here's the link: https://android.googlesource.com/platform/system/bt/+/ea7ab70a711e642653dd5922b83aa04a53af9e0e/stack/include/gatt_api.h but it all display by hex.
for example:
hex
Decimal
reason
0x08
8
connection timeout
0x13
19
connection terminate by peer user
0x16
22
connectionterminated by local host
0x22
34
connection fail for LMP response tout
0x85
133
gatt_error
I am trying to establish and connect to our own vpn (Not the default vpn providers i.e, PPTP, L2TP etc which is present in the Android Setting -> Wireless and Networks) programmatically.
I would like to know if this is already possible as of 2017.
There are reference I use
http://www.thegeekstuff.com/2014/06/android-vpn-service/
https://android.googlesource.com/platform/development/+/master/samples/ToyVpn/src/com/example/android/toyvpn
While using Datagram Channel I get a PortUnreachableException. This is what my Codes look like :
#Override
public void run() {
try {
Log.i(getTag(), "Starting");
// If anything needs to be obtained using the network, get it now.
// This greatly reduces the complexity of seamless handover, which
// tries to recreate the tunnel without shutting down everything.
// In this demo, all we need to know is the server address.
final SocketAddress serverAddress = new InetSocketAddress(mServerName, mServerPort);
// We try to create the tunnel several times.
// TODO: The better way is to work with ConnectivityManager, trying only when the
// network is available.
// Here we just use a counter to keep things simple.
for (int attempt = 0; attempt < 10; ++attempt) {
// Reset the counter if we were connected.
if (run(serverAddress)) {
attempt = 0;
}
// Sleep for a while. This also checks if we got interrupted.
Thread.sleep(3000);
}
Log.i(getTag(), "Giving up");
} catch (IOException | InterruptedException | IllegalArgumentException e) {
Log.e(getTag(), "Connection failed, exiting", e);
}
}
private boolean run(SocketAddress server)
throws IOException, InterruptedException, IllegalArgumentException {
ParcelFileDescriptor iface = null;
boolean connected = false;
// Create a DatagramChannel as the VPN tunnel.
try (DatagramChannel tunnel = DatagramChannel.open()) {
// Protect the tunnel before connecting to avoid loopback.
if (!mService.protect(tunnel.socket())) {
throw new IllegalStateException("Cannot protect the tunnel");
}
// Connect to the server.
tunnel.connect(server);
// For simplicity, we use the same thread for both reading and
// writing. Here we put the tunnel into non-blocking mode.
tunnel.configureBlocking(false);
// Authenticate and configure the virtual network interface.
iface = handshake(tunnel);
// Now we are connected. Set the flag.
connected = true;
// Packets to be sent are queued in this input stream.
FileInputStream in = new FileInputStream(iface.getFileDescriptor());
// Packets received need to be written to this output stream.
FileOutputStream out = new FileOutputStream(iface.getFileDescriptor());
// Allocate the buffer for a single packet.
ByteBuffer packet = ByteBuffer.allocate(MAX_PACKET_SIZE);
// Timeouts:
// - when data has not been sent in a while, send empty keepalive messages.
// - when data has not been received in a while, assume the connection is broken.
long lastSendTime = System.currentTimeMillis();
long lastReceiveTime = System.currentTimeMillis();
// We keep forwarding packets till something goes wrong.
while (true) {
// Assume that we did not make any progress in this iteration.
boolean idle = true;
// Read the outgoing packet from the input stream.
int length = in.read(packet.array());
if (length > 0) {
// Write the outgoing packet to the tunnel.
packet.limit(length);
tunnel.write(packet);
packet.clear();
// There might be more outgoing packets.
idle = false;
lastReceiveTime = System.currentTimeMillis();
}
// Read the incoming packet from the tunnel.
length = tunnel.read(packet);
if (length > 0) {
// Ignore control messages, which start with zero.
if (packet.get(0) != 0) {
// Write the incoming packet to the output stream.
out.write(packet.array(), 0, length);
}
packet.clear();
// There might be more incoming packets.
idle = false;
lastSendTime = System.currentTimeMillis();
}
// If we are idle or waiting for the network, sleep for a
// fraction of time to avoid busy looping.
if (idle) {
Thread.sleep(IDLE_INTERVAL_MS);
final long timeNow = System.currentTimeMillis();
if (lastSendTime + KEEPALIVE_INTERVAL_MS <= timeNow) {
// We are receiving for a long time but not sending.
// Send empty control messages.
packet.put((byte) 0).limit(1);
for (int i = 0; i < 3; ++i) {
packet.position(0);
tunnel.write(packet);
}
packet.clear();
lastSendTime = timeNow;
} else if (lastReceiveTime + RECEIVE_TIMEOUT_MS <= timeNow) {
// We are sending for a long time but not receiving.
throw new IllegalStateException("Timed out");
}
}
}
} catch (SocketException e) {
Log.e(getTag(), "Cannot use socket", e);
} finally {
if (iface != null) {
try {
iface.close();
} catch (IOException e) {
Log.e(getTag(), "Unable to close interface", e);
}
}
}
return connected;
}
And the Error message:
E/ToyVpnConnection[1]: Cannot use socket java.net.PortUnreachableException at sun.nio.ch.DatagramDispatcher.read0(Native Method)
at sun.nio.ch.DatagramDispatcher.read(DatagramDispatcher.java:42)
at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
at sun.nio.ch.IOUtil.read(IOUtil.java:197)
at sun.nio.ch.DatagramChannelImpl.read(DatagramChannelImpl.java:566)
at org.droidplanner.android.fragments.control.ToyVpnConnection.handshake(ToyVpnConnection.java:219)
at org.droidplanner.android.fragments.control.ToyVpnConnection.run(ToyVpnConnection.java:120)
at org.droidplanner.android.fragments.control.ToyVpnConnection.run(ToyVpnConnection.java:93)
MyVpnService:
class MyVpnService extends VpnService{
private static final String TAG = MyVpnService.class.getSimpleName();
private Thread mThread;
private ParcelFileDescriptor mInterface;
//a. Configure a builder for the interface.
Builder builder = new Builder();
public static final String ACTION_CONNECT = "com.example.android.toyvpn.START";
public static final String ACTION_DISCONNECT = "com.example.android.toyvpn.STOP";
private Handler mHandler;
private PendingIntent mConfigureIntent;
private final AtomicReference<Thread> mConnectingThread = new AtomicReference<>();
private final AtomicReference<Connection> mConnection = new AtomicReference<>();
private AtomicInteger mNextConnectionId = new AtomicInteger(1);
private static class Connection extends Pair<Thread, ParcelFileDescriptor> {
public Connection(Thread thread, ParcelFileDescriptor pfd) {
super(thread, pfd);
}
}
#Override
public void onCreate() {
Log.e("MyVpnService","onCreate");
// The handler is only used to show messages.
if (mHandler == null) {
mHandler = new Handler();
}
//Create the intent to "configure" the connection (just start ToyVpnClient).
mConfigureIntent = PendingIntent.getActivity(this, 0, new Intent(this, ToyVpnClient.class),
PendingIntent.FLAG_UPDATE_CURRENT);
}
// Services interface
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Start a new session by creating a new thread.
mThread = new Thread(new Runnable() {
#Override
public void run() {
try {
//a. Configure the TUN and get the interface.
mInterface = builder.setSession("MyVPNService")
.addAddress("192.168.0.1", 24)
.addDnsServer("8.8.8.8")
.addRoute("0.0.0.0", 0).establish();
//b. Packets to be sent are queued in this input stream.
FileInputStream in = new FileInputStream(
mInterface.getFileDescriptor());
//b. Packets received need to be written to this output stream.
FileOutputStream out = new FileOutputStream(
mInterface.getFileDescriptor());
// Allocate the buffer for a single packet.
ByteBuffer packet = ByteBuffer.allocate(32767);
//c. The UDP channel can be used to pass/get ip package to/from server
DatagramChannel tunnel = DatagramChannel.open();
// Connect to the server, localhost is used for demonstration only.
tunnel.connect(new InetSocketAddress("61.31.92.159", 1723));
//tunnel.connect(new InetSocketAddress("127.0.0.1", 8087));
//d. Protect this socket, so package send by it will not be feedback to the vpn service.
protect(tunnel.socket());
//e. Use a loop to pass packets.
while (true) {
//get packet with in
//put packet to tunnel
//get packet form tunnel
//return packet with out
//sleep is a must
Log.e("MyVpnService","true");
Thread.sleep(100);
}
} catch (Exception e) {
// Catch any exception
Log.e(TAG,"Exception:"+e.toString());
e.printStackTrace();
} finally {
try {
if (mInterface != null) {
mInterface.close();
mInterface = null;
}
} catch (Exception e) {
Log.e(TAG,"Exception2:"+e.toString());
}
}
}
}, "MyVpnRunnable");
//start the service
mThread.start();
if (intent != null && ACTION_DISCONNECT.equals(intent.getAction())) {
disconnect();
return START_NOT_STICKY;
} else {
connect();
return START_STICKY;
}
}
#Override
public void onDestroy() {
// TODO Auto-generated method stub
if (mThread != null) {
mThread.interrupt();
}
super.onDestroy();
}
private void connect() {
// Become a foreground service. Background services can be VPN services too, but they can
// be killed by background check before getting a chance to receive onRevoke().
updateForegroundNotification(R.string.connecting);
mHandler.sendEmptyMessage(R.string.connecting);
// Extract information from the shared preferences.
final SharedPreferences prefs = getSharedPreferences(ToyVpnClient.Prefs.NAME, MODE_PRIVATE);
final String server = "61.31.92.159";//prefs.getString(ToyVpnClient.Prefs.SERVER_ADDRESS, "");
final byte[] secret = "123456789".getBytes();//= prefs.getString(ToyVpnClient.Prefs.SHARED_SECRET, "").getBytes();
final int port;
try {
port = Integer.parseInt("1723");//Integer.parseInt(prefs.getString(ToyVpnClient.Prefs.SERVER_PORT, ""));
} catch (NumberFormatException e) {
Log.e("MyVPN", "Bad port: " + prefs.getString(ToyVpnClient.Prefs.SERVER_PORT, null), e);
return;
}
// Kick off a connection.
startConnection(new ToyVpnConnection(
this, mNextConnectionId.getAndIncrement(), server, port, secret));
}
private void disconnect() {
mHandler.sendEmptyMessage(R.string.disconnected);
setConnectingThread(null);
setConnection(null);
stopForeground(true);
}
private void updateForegroundNotification(final int message) {
startForeground(1, new Notification.Builder(this)
//.setSmallIcon(R.drawable.ic_vpn)
.setContentText(getString(message))
.setContentIntent(mConfigureIntent)
.build());
}
private void startConnection(final ToyVpnConnection connection) {
// Replace any existing connecting thread with the new one.
final Thread thread = new Thread(connection, "ToyVpnThread");
setConnectingThread(thread);
// Handler to mark as connected once onEstablish is called.
connection.setConfigureIntent(mConfigureIntent);
connection.setOnEstablishListener(new ToyVpnConnection.OnEstablishListener() {
public void onEstablish(ParcelFileDescriptor tunInterface) {
mHandler.sendEmptyMessage(R.string.connected);
mConnectingThread.compareAndSet(thread, null);
setConnection(new Connection(thread, tunInterface));
}
});
thread.start();
}
private void setConnectingThread(final Thread thread) {
final Thread oldThread = mConnectingThread.getAndSet(thread);
if (oldThread != null) {
oldThread.interrupt();
}
}
private void setConnection(final Connection connection) {
final Connection oldConnection = mConnection.getAndSet(connection);
if (oldConnection != null) {
try {
oldConnection.first.interrupt();
oldConnection.second.close();
} catch (IOException e) {
Log.e(TAG, "Closing VPN interface", e);
}
}
}
}
UPDATE:
a. ToyVPN does not use PPTP protocol (it uses its own)
b. ToyVPN is just a proof-of-concept demo, it does not support multiple {username, password} pairs.
c. API is one for OpenVPN: http://code.google.com/p/ics-openvpn/
This provides one potential VPN solution that you are fully in control of (the server is open-source also), but it is not PPTP or IPSec. If you understand the PPTP protocol, it should be possible to use this as a model to implement such a VPN client.
ToyVPN and OpenVPN doesn't work.
Although it's possible, won't be easy since:
The "VpnService" class is just a Virtual Network Adapter/Interface which can be considered/used as simple Fire-Wall like NetGuard is.
If you want to create a VPN app, that class will just ensure you do not need to root the device.
But all connection and data transfer must be coded from your side, which is hard network stuff.
On the other hand, there are people who did/are doing that even if it's hard to like.
New to Android and developing an app that will deliver Bluetooth data to several different activities. I have a ConnectActivity that performs discovery and presents the devices to the user and when the user selects a device, as suggested by someone in this discussion, starts a BluetoothService.
BluetoothService extends Service and starts a background thread which will eventually be used to block on an InputStream, and broadcast data to activities when it is available (i.e. after those activities poll via startService()).
Here is the Activity. Defines a receiver:
public class ConnectActivity extends AppCompatActivity {
...
// My test receiver just to see if things are working.
private BroadcastReceiver mDataReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if(D)
Log.d(TAG, "Broadcast received.");
}
};
Starts the service onCreate():
protected void onCreate(Bundle savedInstanceState) {
...
IntentFilter dataFilter = new IntentFilter(BluetoothService.INCOMING_DATA);
registerReceiver(mDataReceiver, dataFilter);
// Start the service with an intent identifying the context ("this" is an Activity,
// which inherits from Context) and the recipient.
Intent i = new Intent(this, BluetoothService.class);
i.putExtra(BluetoothService.COMMAND, "MY_COMMAND"); // Add the command as payload to the intent.
if(D)
Log.d(TAG, "Starting service.");
startService(i);
}
And, finally, connect on a button press:
private void connect(){
if(D)
Log.d(TAG, "Connecting to device " + (mBluetoothDevices.get(mSelectedPos).toString()) + " at position " + mSelectedPos);
final ProgressDialog dialog = new ProgressDialog(this);
dialog.setTitle("Connecting");
dialog.setMessage("Please wait...");
dialog.show();
if(D)
Log.d(TAG, "Attempting to connect.");
mBluetoothService.connect(mBluetoothDevices.get(mSelectedPos), dialog);
Here is my service:
public class BluetoothService extends Service {
...
public void connect (BluetoothDevice device, ProgressDialog dialog){
....
// Start the thread to connect to the device
mConnectThread = new ConnectThread(device, dialog, this);
mConnectThread.start();
}
The ConnectThread connects successfully and starts a ConnectedThread which is intended to send data back to the acitvity as it comes in. I am including all of it since this is where my issue is:
public class ConnectedThread extends Thread {
private BluetoothSocket mmSocket;
private final String mmMYNAME = "ConnectedThread";
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private final Context mmContext;
public ConnectedThread(BluetoothSocket socket, Context context) {
mmSocket = socket;
mmContext = context;
InputStream tryIn = null;
OutputStream tryOut = null;
// Get the BluetoothSocket input and output streams
try {
tryIn = mmSocket.getInputStream();
tryOut = mmSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, mmMYNAME + " could not create streams.", e);
}
mmInStream = tryIn;
mmOutStream = tryOut;
mConnectedThreadEnabled = true;
}
#Override
public void run()
{
Thread.currentThread().setName(mmMYNAME);
Intent i = new Intent(INCOMING_DATA);
while(mConnectedThreadEnabled) {
SystemClock.sleep(1000);
if(D)
Log.d(TAG, "ConnectedThread is running.");
LocalBroadcastManager.getInstance(mmContext).sendBroadcast(i); //KABOOM
}
if(D)
Log.d(TAG, "Stopping ConnectedThread.");
}
public void cancel() {
if(D)
Log.d(TAG,"Canceling ConnectedThread.");
try {
if(D)
Log.d(TAG,"Shutting down existing socket.");
mConnectedThreadEnabled = false;
mmSocket.close();
} catch (IOException e) {
Log.e(TAG, "close() of connect socket failed", e);
}
}
}
My problem is that on the line:
LocalBroadcastManager.getInstance(mmContext).sendBroadcast(i); //KABOOM
I am receiving:
java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Context android.content.Context.getApplicationContext()' on a null object reference
at android.content.ContextWrapper.getApplicationContext(ContextWrapper.java:107)
at android.support.v4.content.LocalBroadcastManager.getInstance(LocalBroadcastManager.java:102)
at com.company.me.app.BluetoothService$ConnectedThread.run(BluetoothService.java:255)
I am not clear on what the issue is, given that debug indicates that mmContext and i both seem to be properly created.
which performs a mBluetoothService = new BluetoothService();
Never create instances of components yourself. Get rid of this line. Anything that has been using mBluetoothService needs to be rewritten to:
be in the service itself, or
use service binding (e.g., bindService(), with the activity then calling methods on the supplied Binder), or
use some other pattern, to avoid the activity trying to have a direct reference to the service, let alone creating an instance of the service itself
I am using Bluetooth API of android. I am here creating client-server connection using BluetoothServerSocket & BluetoothSocket but my program stuck at the certain point.
// Create a BroadcastReceiver for ACTION_FOUND
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery find a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// get the BluetoothDevice object from the Intent
BluetoothDevice mBluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.i("MainActivity", "Device Name: " + mBluetoothDevice.getName() + " Address: " + mBluetoothDevice.getAddress());
new AcceptThread().start();
}
}
};
private class AcceptThread extends Thread {
private BluetoothServerSocket mBluetoothServerSocket ;
public AcceptThread() {
try {
mBluetoothServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord("BT_SERVER", UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c666"));
} catch (IOException e) {
Log.e("MainActivity", e.getMessage());
}
}
#Override
public void run() {
BluetoothSocket mBluetoothSocket;
// Keep listening until exception occurs or a socket is returned
while(true) {
try {
mBluetoothSocket = mBluetoothServerSocket.accept();
} catch (IOException e) {
break;
}
// If a connection was accepted
if(mBluetoothSocket != null) {
// transfer the data here
Toast.makeText(MainActivity.this, "Socket is created", Toast.LENGTH_LONG).show();;
try {
// close the connection to stop to listen any connection now
mBluetoothSocket.close();
} catch(IOException e) { }
}
}
}
}
Here my program stuck
mBluetoothServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord("BT_SERVER", UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c666"));
I could not catch why it getting stuck at this point, Any idea to you for this ?
From your question it is unclear whether your application is a client or server or both. For writing bluetooth client-server applications, android phone at any instance plays a single role of server or a client. If your phone is server, then you need to listen for connections from other bluetooth devices using method listenUsingRfcommWithServiceRecord(). Then use accept() to complete the connection.
In case android phone acts as client, it will initiate a bluetooth connection to other devices. For such scenario, your broadcast receiver is needed. We need to scan for available bluetooth devices with startDiscovery() method. Your broadcast receiver's onReceive() is called when a new bluetooth device is found. To connect to this found device, call createRfcommSocketToServiceRecord() with desired UUID.
Hope this helps.
This may be obvious but did you instantiate your BluetoothAdapter? Accept Thread uses the adapter without intializing it.
myBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
While listening, set the discovery name to a specific value then used listenUsingRfcommWithServiceRecord method in broadcast receiver.
private class AcceptTask extends AsyncTask<UUID,Void,BluetoothSocket> {
#Override
protected BluetoothSocket doInBackground(UUID... params) {
String name = mBtAdapter.getName();
try {
//While listening, set the discovery name to a specific value
mBtAdapter.setName(SEARCH_NAME);
BluetoothServerSocket socket = mBtAdapter.listenUsingRfcommWithServiceRecord("BluetoothRecipe", params[0]);
BluetoothSocket connected = socket.accept();
//Reset the BT adapter name
mBtAdapter.setName(name);
return connected;
} catch (IOException e) {
e.printStackTrace();
mBtAdapter.setName(name);
return null;
}
}
#Override
protected void onPostExecute(BluetoothSocket socket) {
if(socket == null) {
return;
}
mBtSocket = socket;
ConnectedTask task = new ConnectedTask();
task.execute(mBtSocket);
}
}
// End
I am building an Android app that communicates with an Arduino board via bluetooth, I have the bluetooth code in a class of it's own called BlueComms. To connect to the device I use the following methord:
public boolean connectDevice() {
CheckBt();
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
Log.d(TAG, "Connecting to ... " + device);
mBluetoothAdapter.cancelDiscovery();
try {
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
btSocket.connect();
outStream = btSocket.getOutputStream();
Log.d(TAG, "Connection made.");
return true;
} catch (IOException e) {
try {
btSocket.close();
} catch (IOException e2) {
Log.d(TAG, "Unable to end the connection");
return false;
}
Log.d(TAG, "Socket creation failed");
}
return false;
}
private void CheckBt() {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (!mBluetoothAdapter.isEnabled()) {
System.out.println("Bt dsbld");
}
if (mBluetoothAdapter == null) {
System.out.println("Bt null");
}
}
This connects fine but as soon as I leave the activity I connected through it drops the connection, showing this through LogCat,
D/dalvikvm(21623): GC_CONCURRENT freed 103K, 10% free 2776K/3056K, paused 5ms+2ms, total 35ms
I can no longer connect to the device, but if I call killBt() it throws a fatal error and if I try to send data I get a 'Socket creation failed' error. My send message code is as follows:
public void sendData(String data, int recvAct) {
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.d(TAG, "Bug BEFORE Sending stuff", e);
}
String message = data;
byte[] msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.d(TAG, "Bug while sending stuff", e);
}
}
How should I go about preventing the connection from being paused by the activity I connect with when I switch a different activity, I am switching activities with this code:
Intent myIntent = new Intent(v.getContext(), Timelapse.class);
startActivityForResult(myIntent, 0);
Many Thanks,
Rozz
Where did you store the instance of your BlueComms class? If you put it in the first activity then the class instance would have been killed when that activity was destroyed as you left it and moved to the next activity (NB activities also get destroyed on screen rotation)
So you need to find a way to keep the instance of BlueComms class alive for as long as you need it. You could pass it between activities via public properties and store it in onRetainNonConfigurationInstance() during rotations.
An easier trick is to create a class that extends Application use it as the application delegate for your app and add public property to it to store the instance of BlueComms class within it. That way the instance of BlueComms class would be alive for the lifetime of you app.
Extend Application
import android.app.Application;
public class cBaseApplication extends Application {
public BlueComms myBlueComms;
#Override
public void onCreate()
{
super.onCreate();
myBlueComms = new BlueComms();
}
}
Make your class the application delegate in the app manifest
<application
android:name="your.app.namespace.cBaseApplication"
android:icon="#drawable/icon"
android:label="#string/app_name" >
Access the base app from any of your Activities like this
((cBaseApplication)this.getApplicationContext()).myBlueComms.SomeMethod();
What I have done is, Created a singleton class for BluetoothConnection.
So socket creation happens only for one time.
When onCreate method of any activity is created, it first fetch instance of BluetoothConnection class.
Handler is used to send messages from thread in BluetoothConnection class to the corresponding activity by settings Handler.
Like:
Class MyBTConnection{
private static MyBTConnection connectionObj;
private Handler mHandler;
public MyBTConnection() { //constructor }
public static MyBTConnection getInstance() {
if(connectionObj == null) {
connectionObj = new MyBTConnection();
}
return connectionObj;
}
}
public void setHandler(Handler handler) {
mHandler = handler;
}
..... Code for Bluetooth Connection ....
to send message :
mHandler.obtainMessage(what).sendToTarget();
}
// in first activity
class MainActivity extends Activity {
private MyBTConnection connectionObj;
public onCreate(....) {
/*
* Since this is first call for getInstance. A new object
* of MyBTConnection will be created and a connection to
* remote bluetooth device will be established.
*/
connectionObj = MyBTConnection.getInstance();
connectionObj.setHandler(mHandler);
}
private Handler mHandler = new Handler(){
public void onReceive(...) {
/// handle received messages here
}
};
}
// in second activity
class SecondActivity extends Activity {
private MyBTConnection connectionObj;
public onCreate(....) {
/*
* Since this is second call for getInstance.
* Object for MyBTConnection was already created in previous
* activity. So getInstance will return that previously
* created object and in that object, connection to remote
* bluetooth device is already established so you can
* continue your work here.
*/
connectionObj = MyBTConnection.getInstance();
connectionObj.setHandler(mHandler);
}
private Handler mHandler = new Handler(){
public void onReceive(...) {
/// handle received messages here
}
};
}
I'm currently having exactly the same issue and I was thinking of opening/closing the Bluetooth socket each time an Activity asks for it. Each Activity has it's own BlueComms instance.
Because my application will became a bit complex and there will be Bluetooth threaded requests from different activities, I'm thinking that this way will become very difficult to use and troubleshoot.
Another way I came across by reading here...
https://developer.android.com/guide/components/services.html
A Service can be created on the background having a Bluetooth socket always on. All Bluetooth requests can be made using Intent towards this service. This also creates some fair amount of complexity but feels a lot more tidy and organized.
I'm currently having this dilemma, either to use a thread for each activity or use a service. I don't know which way is actually better.
When you are Selecting A device to connect and when you are click on the device list item for requesting a connection to the device use AsyncTask
and put the connect method inside the AsyncTask like this :-
AsyncTask.execute(new Runnable() {
#Override
public void run() {
try {
bluetoothSocket = Globals.bluetoothDevice.createRfcommSocketToServiceRecord(Globals.DEFAULT_SPP_UUID);
bluetoothSocket.connect();
// After successful connect you can open InputStream
} catch (IOException e) {
e.printStackTrace();
}
**Here is the full code for the same problem that i have cracked :-**
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
lablelexconnected.setText("Connecting ...");
bdDevice = arrayListBluetoothDevices.get(position);
//bdClass = arrayListBluetoothDevices.get(position)
// Toast.makeText(getApplicationContext()," " + bdDevice.getAddress(),Toast.LENGTH_SHORT).show();
Log.i("Log", "The dvice : " + bdDevice.toString());
bdDevice = bluetoothAdapter.getRemoteDevice(bdDevice.getAddress());
Globals.bluetoothDevice = bluetoothAdapter.getRemoteDevice(bdDevice.getAddress());
System.out.println("Device in GPS Settings : " + bdDevice);
// startService(new Intent(getApplicationContext(),MyService.class));
/* Intent i = new Intent(GpsSettings.this, MyService.class);
startService(i);*/
// finish();
// connectDevice();
AsyncTask.execute(new Runnable() {
#Override
public void run() {
try {
bluetoothSocket = Globals.bluetoothDevice.createRfcommSocketToServiceRecord(Globals.DEFAULT_SPP_UUID);
bluetoothSocket.connect();
// After successful connect you can open InputStream
InputStream in = null;
in = bluetoothSocket.getInputStream();
InputStreamReader isr = new InputStreamReader(in);
br = new BufferedReader(isr);
while (found == 0) {
String nmeaMessage = br.readLine();
Log.d("NMEA", nmeaMessage);
// parse NMEA messages
sentence = nmeaMessage;
System.out.println("Sentence : " + sentence);
if (sentence.startsWith("$GPRMC")) {
String[] strValues = sentence.split(",");
System.out.println("StrValues : " + strValues[3] + " " + strValues[5] + " " + strValues[8]);
if (strValues[3].equals("") && strValues[5].equals("") && strValues[8].equals("")) {
Toast.makeText(getApplicationContext(), "Location Not Found !!! ", Toast.LENGTH_SHORT).show();
} else {
latitude = Double.parseDouble(strValues[3]);
if (strValues[4].charAt(0) == 'S') {
latitude = -latitude;
}
longitude = Double.parseDouble(strValues[5]);
if (strValues[6].charAt(0) == 'W') {
longitude = -longitude;
}
course = Double.parseDouble(strValues[8]);
// Toast.makeText(getApplicationContext(), "latitude=" + latitude + " ; longitude=" + longitude + " ; course = " + course, Toast.LENGTH_SHORT).show();
System.out.println("latitude=" + latitude + " ; longitude=" + longitude + " ; course = " + course);
// found = 1;
NMEAToDecimalConverter(latitude, longitude);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
});