1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-28 04:46:51 +01:00

Huawei: fix race condition in BtBR

Should be a fix for #3914.
This commit is contained in:
Martin.JM 2024-07-22 20:23:19 +02:00
parent 20f4248e1c
commit b8a2fc0076
2 changed files with 138 additions and 74 deletions

View File

@ -57,80 +57,8 @@ public final class BtBRQueue {
private final Context mContext;
private final int mBufferSize;
private Handler mWriteHandler;
private final HandlerThread mWriteHandlerThread = new HandlerThread("Write Thread", Process.THREAD_PRIORITY_BACKGROUND) {
@Override
protected void onLooperPrepared() {
LOG.debug("Write handler thread's looper prepared, creating write handler");
mWriteHandler = new Handler(mWriteHandlerThread.getLooper()) {
@SuppressLint("MissingPermission")
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case HANDLER_SUBJECT_CONNECT: {
try {
mBtSocket.connect();
LOG.info("Connected to RFCOMM socket for {}", mGbDevice.getName());
setDeviceConnectionState(GBDevice.State.CONNECTED);
// update thread names to show device names in logs
readThread.setName(String.format(Locale.ENGLISH,
"Read Thread for %s", mGbDevice.getName()));
mWriteHandlerThread.setName(String.format(Locale.ENGLISH,
"Write Thread for %s", mGbDevice.getName()));
// now that connect has been created, start the threads
readThread.start();
onConnectionEstablished();
} catch (IOException e) {
LOG.error("IO exception while establishing socket connection: ", e);
setDeviceConnectionState(GBDevice.State.NOT_CONNECTED);
}
return;
}
case HANDLER_SUBJECT_PERFORM_TRANSACTION: {
try {
if (!isConnected()) {
LOG.debug("Not connected, updating device state to WAITING_FOR_RECONNECT");
setDeviceConnectionState(GBDevice.State.WAITING_FOR_RECONNECT);
return;
}
if (!(msg.obj instanceof Transaction)) {
LOG.error("msg.obj is not an instance of Transaction");
return;
}
Transaction transaction = (Transaction) msg.obj;
for (BtBRAction action : transaction.getActions()) {
if (LOG.isDebugEnabled()) {
LOG.debug("About to run action: {}", action);
}
if (action.run(mBtSocket)) {
LOG.debug("Action ok: {}", action);
} else {
LOG.error("Action returned false, cancelling further actions in transaction: {}", action);
break;
}
}
} catch (Throwable ex) {
LOG.error("IO Write Thread died: " + ex.getMessage(), ex);
}
return;
}
}
LOG.warn("Unhandled write handler message {}", msg.what);
}
};
}
};
private final Handler mWriteHandler;
private final HandlerThread mWriteHandlerThread = new HandlerThread("Write Thread", Process.THREAD_PRIORITY_BACKGROUND);
private Thread readThread = new Thread("Read Thread") {
@Override
@ -176,6 +104,74 @@ public final class BtBRQueue {
mBufferSize = bufferSize;
mWriteHandlerThread.start();
LOG.debug("Write handler thread is prepared, creating write handler");
mWriteHandler = new Handler(mWriteHandlerThread.getLooper()) {
@SuppressLint("MissingPermission")
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case HANDLER_SUBJECT_CONNECT: {
try {
mBtSocket.connect();
LOG.info("Connected to RFCOMM socket for {}", mGbDevice.getName());
setDeviceConnectionState(GBDevice.State.CONNECTED);
// update thread names to show device names in logs
readThread.setName(String.format(Locale.ENGLISH,
"Read Thread for %s", mGbDevice.getName()));
mWriteHandlerThread.setName(String.format(Locale.ENGLISH,
"Write Thread for %s", mGbDevice.getName()));
// now that connect has been created, start the threads
readThread.start();
onConnectionEstablished();
} catch (IOException e) {
LOG.error("IO exception while establishing socket connection: ", e);
setDeviceConnectionState(GBDevice.State.NOT_CONNECTED);
}
return;
}
case HANDLER_SUBJECT_PERFORM_TRANSACTION: {
try {
if (!isConnected()) {
LOG.debug("Not connected, updating device state to WAITING_FOR_RECONNECT");
setDeviceConnectionState(GBDevice.State.WAITING_FOR_RECONNECT);
return;
}
if (!(msg.obj instanceof Transaction)) {
LOG.error("msg.obj is not an instance of Transaction");
return;
}
Transaction transaction = (Transaction) msg.obj;
for (BtBRAction action : transaction.getActions()) {
if (LOG.isDebugEnabled()) {
LOG.debug("About to run action: {}", action);
}
if (action.run(mBtSocket)) {
LOG.debug("Action ok: {}", action);
} else {
LOG.error("Action returned false, cancelling further actions in transaction: {}", action);
break;
}
}
} catch (Throwable ex) {
LOG.error("IO Write Thread died: " + ex.getMessage(), ex);
}
return;
}
}
LOG.warn("Unhandled write handler message {}", msg.what);
}
};
}
/**

View File

@ -0,0 +1,68 @@
/* Copyright (C) 2022-2023 Martin.JM
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.btbr.BtBRQueue;
@RunWith(MockitoJUnitRunner.class)
public class TestBtBRQueue {
@Test
public void connect() {
GBDevice device = Mockito.mock(GBDevice.class);
when(device.isConnected()).thenReturn(false);
BluetoothDevice btDevice = Mockito.mock(BluetoothDevice.class);
BluetoothAdapter btAdapter = Mockito.mock(BluetoothAdapter.class);
when(btAdapter.getRemoteDevice((String) any())).thenReturn(btDevice);
BtBRQueue queue = new BtBRQueue(btAdapter, device, null, null, null, 512);
Assert.assertTrue(queue.connect());
}
@Test
public void reconnect() {
GBDevice device = Mockito.mock(GBDevice.class);
when(device.isConnected()).thenReturn(false);
BluetoothDevice btDevice = Mockito.mock(BluetoothDevice.class);
BluetoothAdapter btAdapter = Mockito.mock(BluetoothAdapter.class);
when(btAdapter.getRemoteDevice((String) any())).thenReturn(btDevice);
BtBRQueue queue = new BtBRQueue(btAdapter, device, null, null, null, 512);
Assert.assertTrue(queue.connect());
queue.disconnect();
Assert.assertTrue(queue.connect());
}
}