public class BluetoothConnectionService extends Service {
private ScaleState currentState = [Link];
public interface BluetoothDisconnectCallback {
void onDisconnected();
}
public interface TareCallback {
void onTareSet(boolean success);
}
private BlockingQueue<String> responseQueue = new
LinkedBlockingQueue<>();
private static final long RESPONSE_TIMEOUT = 10000; // 5 seconds
private static final int MAX_RETRY_COUNT = 5; // Define your max retry
count
private static final long RETRY_INTERVAL = 5000; // Define your retry
interval in milliseconds
private String lastConnectedDeviceAddress; // Ensure this is set when you
connect to a device
private boolean responseReceived = false;
private String tareSetResponse1 = "T S";
private String tareSetResponse2 = "MT S";
private String tareClearedResponse = "CT S";
private static final long COMMAND_TIMEOUT = 10000; // 5 seconds
private final LinkedBlockingQueue<String> commandQueue = new
LinkedBlockingQueue<>();
private boolean isProcessingCommands = false;
private static final String TARE_COMMAND = "T\r\n";
private static final String CLEAR_TARE_COMMAND = "CT\r\n";
private static final long TARE_RESPONSE_TIMEOUT = 5000;
private TareCallback tareCallback;
private static final int CONNECTION_TIMEOUT_MS = 10000; // Timeout for
connection attempts in milliseconds
private static final int NOTIFICATION_ID = 1; // Notification ID for
foreground service
private static final String CHANNEL_ID =
"BluetoothConnectionServiceChannel"; // Notification channel ID
private Handler connectionTimeoutHandler;
private boolean tareActive = false; // Private member variable to track
tare status
private WeightScaleManager weightScaleManager;
private BluetoothDevice connectedDevice;
private BluetoothSocket socket;
private boolean isConnected = false;
private boolean isConnecting = false; // Track if a connection attempt is
in progress
private static final int BLUETOOTH_PERMISSION_REQUEST_CODE = 1;
private static final UUID SPP_UUID = [Link]("00001101-0000-1000-
8000-00805F9B34FB");
private ReadsViewModel viewModel;
private static BluetoothConnectionService instance;
private BluetoothDisconnectCallback disconnectCallback; // Callback to
notify disconnection
public static final String ACTION_CONNECT =
"[Link].ACTION_CONNECT";
public BluetoothConnectionService() {
// Constructor - no initialization needed here
}
public static synchronized BluetoothConnectionService getInstance() {
if (instance == null) {
instance = new BluetoothConnectionService();
}
return instance;
}
// Add more potential tare success responses
private final List<String> tareSuccessResponses = [Link](
"T S", "MT S", "Tare Set", "Tare Success", "0.00", "0.000"
);
@Override
public void onCreate() {
[Link]();
instance = this; // Set the singleton instance
weightScaleManager = new WeightScaleManager(this); // Correctly
passing the service context
viewModel = [Link](this);
// Create a notification channel for the foreground service
createNotificationChannel();
}
private void enqueueCommand(String command) {
[Link](command);
if (!isProcessingCommands) {
processCommandQueue();
}
}
private void processCommandQueue() {
new Thread(() -> {
isProcessingCommands = true;
while (![Link]()) {
String command = [Link]();
boolean success = sendCommand(command);
if ([Link](TARE_COMMAND) ||
[Link](CLEAR_TARE_COMMAND)) {
handleTareResult(success, [Link](TARE_COMMAND));
}
try {
[Link](1000); // Wait between commands
} catch (InterruptedException e) {
Log.e(TAG, "Command processing interrupted", e);
}
}
isProcessingCommands = false;
}).start();
}
private void handleTareResult(boolean success, boolean isSetting) {
if (success) {
updateScaleState(isSetting ? [Link] :
ScaleState.TARE_CLEARED);
} else {
// If tare operation failed, revert to previous state or WEIGHING
updateScaleState(currentState == [Link] ?
[Link] : currentState);
}
handleTareStatus(success && isSetting);
if (tareCallback != null) {
new Handler([Link]()).post(() ->
[Link](success));
}
}
public ScaleState getCurrentState() {
return currentState;
}
public void setTare() {
Log.d(TAG, "setTare() called. Current state: " + currentState);
if (currentState != [Link]) {
Log.i(TAG, "Enqueueing TARE_COMMAND");
enqueueCommand(TARE_COMMAND);
} else {
Log.i(TAG, "Scale is already tared");
}
}
public void clearTare() {
if (currentState == [Link]) {
enqueueCommand(CLEAR_TARE_COMMAND);
} else {
Log.i(TAG, "Scale is not currently tared");
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
// Start the service in the foreground immediately to avoid the
RemoteServiceException
startForegroundServiceWithNotification();
String action = [Link]();
if (ACTION_CONNECT.equals(action)) {
String deviceAddress =
[Link]("DEVICE_ADDRESS");
if (deviceAddress != null) {
connectToDevice(deviceAddress);
} else {
Log.i("BluetoothConnectionService", "No device address
provided.");
}
}
}
return START_STICKY;
}
private void startForegroundServiceWithNotification() {
Notification notification = new [Link](this,
CHANNEL_ID)
.setContentTitle("Bluetooth Connection")
.setContentText("Connecting to the device...")
.setSmallIcon([Link].ic_bluetooth)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build();
startForeground(NOTIFICATION_ID, notification);
}
private void createNotificationChannel() {
if ([Link].SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Bluetooth Connection Service";
String description = "Channel for Bluetooth Connection Service";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
name, importance);
[Link](description);
NotificationManager notificationManager =
getSystemService([Link]);
if (notificationManager != null) {
[Link](channel);
}
}
}
public boolean isConnected() {
return isConnected;
}
public boolean isConnecting() {
return isConnecting;
}
public void setDisconnectCallback(BluetoothDisconnectCallback callback) {
[Link] = callback;
}
public void setTareCallback(TareCallback callback) {
[Link] = callback;
}
public void connectToDevice(String deviceAddress) {
if (isConnected || isConnecting) {
Log.i("BluetoothConnectionService", "Already in connection
process.");
return; // Exit if already connected or in connecting process
}
if (!checkBluetoothPermissions()) {
Log.e("BluetoothConnectionService", "Bluetooth permissions not
granted");
requestBluetoothPermissions();
onConnectionStateChanged([Link]);
return;
}
setConnecting(true);
onConnectionStateChanged([Link]);
Log.i("BluetoothConnectionService", "Connecting to device: " +
deviceAddress);
connectionTimeoutHandler = new Handler([Link]());
[Link](() -> {
if (isConnecting) {
Log.e("BluetoothConnectionService", "Connection attempt timed
out.");
handleConnectionFailure("Connection timed out. Please try
again.");
}
}, CONNECTION_TIMEOUT_MS);
new Thread(() -> {
try {
if (socket != null) {
disconnect();
}
connectedDevice =
[Link]().getRemoteDevice(deviceAddress);
socket =
[Link](SPP_UUID);
[Link]();
isConnected = true;
[Link](null);
Log.i("BluetoothConnectionService", "Connection
successful.");
onConnectionStateChanged([Link]);
startReading();
} catch (IOException e) {
Log.e("BluetoothConnectionService", "Connection failed", e);
handleConnectionFailure("Connection failed. Please check the
device.");
} catch (SecurityException e) {
Log.e("BluetoothConnectionService", "Bluetooth permission
denied", e);
handleConnectionFailure("Bluetooth permission denied. Please
enable permissions.");
} catch (Exception e) {
Log.e("BluetoothConnectionService", "Unexpected error", e);
handleConnectionFailure("An unexpected error occurred: " +
[Link]());
}
}).start();
}
private void handleConnectionFailure(String message) {
isConnected = false;
setConnecting(false);
[Link](null);
onConnectionStateChanged([Link]);
showToast(message);
}
private void setConnecting(boolean connecting) {
isConnecting = connecting;
Log.d("BluetoothConnectionService", "isConnecting set to: " +
isConnecting);
}
private void showToast(String message) {
new Handler([Link]()).post(() -> {
[Link](this, message, Toast.LENGTH_SHORT).show();
});
}
private boolean checkBluetoothPermissions() {
if ([Link].SDK_INT >= Build.VERSION_CODES.S) {
return [Link](this,
[Link].BLUETOOTH_CONNECT)
== PackageManager.PERMISSION_GRANTED;
} else {
return [Link](this,
[Link])
== PackageManager.PERMISSION_GRANTED;
}
}
private void requestBluetoothPermissions() {
// Requesting permissions directly from the service is not
recommended
// Permission requests should generally be handled from an Activity
or Fragment
}
private void startReading() {
new Thread(() -> {
byte[] buffer = new byte[1024];
int bytes;
while (isConnected) {
try {
bytes = [Link]().read(buffer);
logRawBytes(buffer); // Log raw bytes for debugging
String data = new String(buffer, 0, bytes).trim();
if (![Link]()) {
Log.d(TAG, "Received data: " + data);
[Link](data);
[Link](data);
// Process weight data using robust parsing
Double parsedWeight = parseWeightResponse(data);
if (parsedWeight != null) {
float weight = [Link]();
String unit = [Link]();
boolean isStable =
[Link]();
new Handler([Link]()).post(() -> {
handleWeightUpdate(weight, unit, isStable);
sendWeightUpdate(weight, unit, isStable);
broadcastWeightData(data);
});
} else {
Log.w(TAG, "Failed to parse weight from data: " +
data);
}
}
} catch (IOException e) {
handleDisconnection();
Log.e(TAG, "Error reading data", e);
break;
}
}
cleanupConnection();
}).start();
}
private void logRawBytes(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
[Link]([Link]("%02X", b));
}
Log.d(TAG, "Raw bytes: " + [Link]());
Log.d(TAG, "As UTF-8 string: " + new String(bytes,
StandardCharsets.UTF_8));
Log.d(TAG, "As ISO-8859-1 string: " + new String(bytes,
StandardCharsets.ISO_8859_1));
}
private void updateScaleState(ScaleState newState) {
currentState = newState;
Log.i(TAG, "Scale state changed to: " + newState);
new Handler([Link]()).post(() -> {
if (viewModel != null) {
[Link](newState);
} else {
Log.e(TAG, "ViewModel is null in updateScaleState");
}
});
}
private void cleanupConnection() {
disconnect();
if (disconnectCallback != null) {
[Link]();
}
}
private void broadcastConnectionStatus(boolean isConnected) {
Intent intent = new Intent("BLUETOOTH_CONNECTION_STATUS");
[Link]("IS_CONNECTED", isConnected);
[Link](this).sendBroadcast(intent);
}
private void handleDisconnection() {
closeSocket();
isConnected = false;
onConnectionStateChanged([Link]);
showToast("Device disconnected.");
Log.e(TAG, "Disconnected from device");
// Reset state to IDLE
updateScaleState([Link]);
// Attempt to reconnect
new Thread(() -> {
int retryCount = 0;
while (!isConnected && retryCount < MAX_RETRY_COUNT) {
try {
[Link](RETRY_INTERVAL);
connectToDevice(lastConnectedDeviceAddress);
retryCount++;
} catch (InterruptedException e) {
Log.e(TAG, "Reconnection attempt interrupted", e);
}
}
if (!isConnected) {
Log.e(TAG, "Failed to reconnect after " + MAX_RETRY_COUNT + "
attempts");
}
}).start();
}
private void closeSocket() {
try {
if (socket != null) {
[Link]();
}
} catch (IOException e) {
Log.e("BluetoothConnectionService", "Error closing socket", e);
}
}
private void broadcastWeightData(String data) {
Intent intent = new Intent("WEIGHT_DATA");
[Link]("DATA", data);
[Link](this).sendBroadcast(intent);
}
private void handleWeightUpdate(float weight, String unit, boolean
isStable) {
if (viewModel != null) {
WeightData weightData = new WeightData(weight, unit, isStable);
[Link](weightData);
// Update state based on weight and stability
if (isStable && weight == 0 && currentState != [Link])
{
updateScaleState([Link]);
} else if (isStable && currentState != [Link]) {
updateScaleState([Link]);
}
} else {
Log.e("BluetoothConnectionService", "ViewModel is null in
handleWeightUpdate");
}
}
public void disconnect() {
if (socket != null) {
try {
[Link]();
Log.i("BluetoothConnectionService", "Socket closed
successfully.");
} catch (IOException e) {
Log.e("BluetoothConnectionService", "Error closing socket",
e);
} finally {
socket = null;
}
}
isConnected = false;
onConnectionStateChanged([Link]);
setConnecting(false);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
[Link]();
disconnect(); // Ensure the Bluetooth connection is closed
}
private void sendWeightUpdate(float weight, String unit, boolean
isStable) {
WeightData weightData = new WeightData(weight, unit, isStable);
Intent intent = new Intent("WEIGHT_UPDATE");
[Link]("WEIGHT_DATA", weightData); // WeightData is
Parcelable
[Link](this).sendBroadcast(intent);
}
private void onConnectionStateChanged([Link]
connectionState) {
Handler mainHandler = new Handler([Link]());
[Link](() -> {
if (viewModel != null) {
[Link](connectionState);
} else {
Log.e("BluetoothConnectionService", "ViewModel is null in
onConnectionStateChanged");
}
broadcastConnectionStatus(connectionState ==
[Link]);
});
}
private boolean sendCommand(String command) {
if (!isConnected || socket == null) {
Log.e(TAG, "Cannot send command, device not connected.");
return false;
}
Log.d(TAG, "Attempting to send command: " + [Link]());
CompletableFuture<Boolean> future = new CompletableFuture<>();
new Thread(() -> {
try {
byte[] commandBytes = [Link]();
[Link]().write(commandBytes);
[Link]().flush();
Log.i(TAG, "Command sent successfully: " + [Link]());
logRawBytes(commandBytes);
String response =
waitForResponse([Link](TARE_COMMAND) ? TARE_RESPONSE_TIMEOUT :
RESPONSE_TIMEOUT);
Log.d(TAG, "Response received: " + (response != null ?
response : "null"));
boolean success = (response != null) &&
handleResponse(response, command);
Log.i(TAG, "Command execution result: " + (success ?
"Success" : "Failure"));
if ([Link](TARE_COMMAND) && success) {
updateScaleState([Link]);
handleTareStatus(true);
} else if ([Link](CLEAR_TARE_COMMAND) && success) {
updateScaleState(ScaleState.TARE_CLEARED);
handleTareStatus(false);
}
[Link](success);
} catch (IOException e) {
Log.e(TAG, "Failed to send command: " + [Link](), e);
[Link](false);
}
}).start();
try {
boolean result = [Link](COMMAND_TIMEOUT,
[Link]);
Log.d(TAG, "Command execution completed with result: " + result);
return result;
} catch (InterruptedException | ExecutionException | TimeoutException
e) {
Log.e(TAG, "Command execution failed or timed out: " +
[Link](), e);
return false;
}
}
private String waitForResponse(long timeout) {
try {
String response = [Link](timeout,
[Link]);
Log.d(TAG, "Received response: " + (response != null ? response :
"null") + " after waiting for " + timeout + "ms");
return response;
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for response", e);
return null;
}
}
private boolean handleResponse(String response, String command) {
Log.d(TAG, "Handling response: " + response + " for command: " +
command);
boolean success = false;
if ([Link](TARE_COMMAND)) {
success = isTareSuccessResponse(response);
Log.i(TAG, "Tare command response: " + response + ", Success: " +
success);
if (success) {
updateScaleState([Link]);
handleTareStatus(true);
} else {
// Handle potential protocol-specific indicators of tare
failure
if ([Link]("ε") || [Link]("error")) {
success = false; // Explicitly mark failure if certain
characters are present
Log.w(TAG, "Detected potential tare failure indication in
response.");
} else {
// Check for weight near zero as a possible indication of
tare success
Double parsedWeight = parseWeightResponse(response);
if (parsedWeight != null && [Link](parsedWeight) <
0.01) {
success = true;
Log.i(TAG, "Tare successful based on weight near
zero");
updateScaleState([Link]);
handleTareStatus(true);
}
}
}
} else if ([Link](CLEAR_TARE_COMMAND)) {
success = [Link]("CT S");
Log.i(TAG, "Clear tare command response: " + (success ? "Success"
: "Failure"));
if (success) {
updateScaleState(ScaleState.TARE_CLEARED);
handleTareStatus(false);
}
}
return success;
}
private boolean isTareSuccessResponse(String response) {
return [Link]().anyMatch(response::contains) ||
([Link](".*[Tt]are.*[Ss]uccess.*")) ||
([Link](".*[Tt]are.*[Ss]et.*"));
}
public boolean isTareActive() {
// Implement logic to check if tare is active based on tareActive
variable
return tareActive;
}
private static final String TAG = "BluetoothConnectionService";
public void handleTareStatus(boolean isTareActive) {
tareActive = isTareActive;
Log.i(TAG, "Tare status updated: " + (isTareActive ? "Active" :
"Inactive"));
new Handler([Link]()).post(() -> {
if (viewModel != null) {
[Link](isTareActive);
} else {
Log.e(TAG, "ViewModel is null in handleTareStatus");
}
broadcastTareStatus(isTareActive);
});
}
private void broadcastTareStatus(boolean isTareActive) {
Intent intent = new Intent("TARE_STATUS_UPDATE");
[Link]("IS_TARE_ACTIVE", isTareActive);
[Link](this).sendBroadcast(intent);
}
enum ScaleState {
IDLE, TARED, TARE_CLEARED, WEIGHING
}
private Double parseWeightResponse(String response) {
Pattern pattern = [Link]("(-?\\d+(?:\\.\\d+)?)");
Matcher matcher = [Link](response);
if ([Link]()) {
try {
return [Link]([Link](1));
} catch (NumberFormatException e) {
Log.e(TAG, "Failed to parse weight", e);
}
}
return null;
}