Skip to content

Instantly share code, notes, and snippets.

@dragonlord2025
Last active April 16, 2025 04:39
Show Gist options
  • Select an option

  • Save dragonlord2025/86b05ae10dbd6456aa53 to your computer and use it in GitHub Desktop.

Select an option

Save dragonlord2025/86b05ae10dbd6456aa53 to your computer and use it in GitHub Desktop.
Recording From Bluetooth Headset
package com.example.audiogain;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.os.CountDownTimer;
import android.widget.Toast;
public class Recording {
static int count = 0;
static String Shared;
static String bFlag;
public static int TIMEOUT = 5000;
public static int COUNTDOWN_INTERVAL = 1000;
static Context context;
public static void checkAndRecord(Context context,
OnBluetoothRecording BluetoothRecording, boolean resume) {
// Check bluetooth flag And Bluetooth is ON or OFF
if (getBluetoothFlag(context) && isBluetoothON()) {
// Check for bluetooth and Record
startBluetoothRecording(BluetoothRecording, resume, context);
} else {
// If Bluetooth is OFF Show Toast else Dont Show
if (getBluetoothFlag(context) && !isBluetoothON()) {
// false because recording not started
Toast.makeText(context,
"Bluetooth is OFF. Recording from Phone MIC.",
Toast.LENGTH_SHORT).show();
BluetoothRecording.onStartRecording(resume, false);
} else {
// false because recording not started
BluetoothRecording.onStartRecording(resume, false);
}
}
}
private static void startBluetoothRecording(
final OnBluetoothRecording BluetoothRecording,
final boolean resume, Context context) {
// TODO Auto-generated method stub
final int MAX_ATTEPTS_TO_CONNECT = 3;
final AudioManager audioManager = (AudioManager) context
.getSystemService(Context.AUDIO_SERVICE);
final CountDownTimer timer = getTimer(BluetoothRecording, audioManager,
resume);
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(
AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
if (AudioManager.SCO_AUDIO_STATE_CONNECTED == state) {
// cancel Timer
timer.cancel();
context.unregisterReceiver(this);
// pass through and true because
// recording from bluetooth so set 8000kHz
BluetoothRecording.onStartRecording(resume, true);
} else if (AudioManager.SCO_AUDIO_STATE_DISCONNECTED == state) {
if (count > MAX_ATTEPTS_TO_CONNECT) {
context.unregisterReceiver(this);
// Stop BluetoothSCO
audioManager.stopBluetoothSco();
// reset Counter
count = 0;
// stop timer
timer.cancel();
// false because still recording not started
BluetoothRecording.onStartRecording(resume, false);
} else {
// Increment Disconnect state Count
count++;
}
}
}
}, new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED));
// Start the timer
timer.start();
audioManager.startBluetoothSco();
}
// set the Timeout
private static CountDownTimer getTimer(
final OnBluetoothRecording BluetoothRecording,
final AudioManager audioManager, final boolean resume) {
// TODO Auto-generated method stub
return new CountDownTimer(TIMEOUT, COUNTDOWN_INTERVAL) {
@Override
public void onTick(long millisUntilFinished) {
// Do Nothing
}
@Override
public void onFinish() {
// stopBluetoothSCO() and start Normal Recording
audioManager.stopBluetoothSco();
// false because recording button is already clicked but still
// not recording.
BluetoothRecording.onStartRecording(resume, false);
}
};
}
// Return's the bluetooth state
private static boolean isBluetoothON() {
BluetoothAdapter bluetoothAdapter = BluetoothAdapter
.getDefaultAdapter();
return bluetoothAdapter.isEnabled();
}
// Return's the bluetoothFlag state
private static boolean getBluetoothFlag(Context context) {
// shared pref
SharedPreferences sp = context.getSharedPreferences(Shared,
Context.MODE_PRIVATE);
return sp.getBoolean(bFlag, false);
}
}
@vuckoo81
Copy link

Help mate. I need usage example

@albaspazio
Copy link

dear shivarajp....what is OnBluetoothRecording ??
I can read this type/class in several posts in internet....but none of them defines it
thanks in advance
Alberto

@Dallasson
Copy link

Hey mate i have the tried the same code but it keep going to Disconnected bloc , any reason why !! bluetooth is on , device is connected bu when i execute the code , it never goes to connected ..

@GmdDev074
Copy link

GmdDev074 commented Apr 15, 2025

Great working for me be low is my code

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.os.Binder;
import android.os.Build;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;

import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.ServiceCompat;
import androidx.lifecycle.MutableLiveData;

import com.dev.auditiontech.R;
import com.dev.auditiontech.activity.MainActivity;

public class MonitorServiceMic extends Service {
private static final String TAG = "MonitorServiceMic";
public static MonitorServiceMic service;
private NotificationManager nm;
private NotificationCompat.Builder nCB;
private static final String CHANNEL_ID = "MonitoringChannel";
private static final int NOTIFICATION_ID = 1001;
private AudioRecord audioRecord;
private AudioManager audioManager;
private BluetoothAdapter bluetoothAdapter;
private BluetoothHeadset bluetoothHeadset;
private String micSourceName = "";
private boolean isBluetoothMic = false;
private BluetoothDevice connectedBluetoothDevice = null;
private double lastDecibel = 0.0;
private String lastSafeFor = "N/A";
private double lastNotifiedDecibel = -1.0;
private String lastNotifiedSafeFor = "";
private final IBinder binderMic = new LocalBinder();
private Handler micUpdateHandler;
private MutableLiveData volume = new MutableLiveData<>();
private boolean isRecording = false;
private boolean isInitializing = false;
private static final int SAMPLE_RATE = 8000; // Optimized for Bluetooth
private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
private static final int BUFFER_SIZE = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT) * 2;
private static final double BASE = 32767.0; // Max amplitude for 16-bit PCM
private static final long RECONNECT_DELAY_MS = 5000;
private static final int SCO_TIMEOUT_MS = 5000;
private static final int SCO_COUNTDOWN_INTERVAL_MS = 1000;
private static final int MAX_SCO_ATTEMPTS = 3;

private Runnable micUpdateRunnable = new Runnable() {
    @Override
    public void run() {
        updateMicStatus();
        micUpdateHandler.postDelayed(this, 200);
    }
};

private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
    @SuppressLint("MissingPermission")
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            String deviceName = device != null && device.getName() != null ? device.getName() : "Unknown Device";
            if (state == BluetoothProfile.STATE_CONNECTED) {
                Log.d(TAG, "HFP connected: " + deviceName);
                connectedBluetoothDevice = device;
                micSourceName = deviceName;
                isBluetoothMic = true;
                handleBluetoothReconnect();
            } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
                Log.d(TAG, "HFP disconnected: " + deviceName);
                connectedBluetoothDevice = null;
                isBluetoothMic = false;
                micSourceName = getDeviceName();
                reinitializeAudioRecord();
            }
        } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
            if (state == BluetoothAdapter.STATE_OFF) {
                Log.d(TAG, "Bluetooth disabled");
                connectedBluetoothDevice = null;
                isBluetoothMic = false;
                micSourceName = getDeviceName();
                reinitializeAudioRecord();
            } else if (state == BluetoothAdapter.STATE_ON) {
                Log.d(TAG, "Bluetooth enabled");
                initializeBluetoothProfile();
            }
        }
    }
};

private BroadcastReceiver scoReceiver;

@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onCreate() {
    super.onCreate();
    Log.d(TAG, "Service created");
    nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    micUpdateHandler = new Handler(Looper.getMainLooper());
    micSourceName = getDeviceName();
    createNotificationChannel();
    nCB = new NotificationCompat.Builder(this, CHANNEL_ID);
    initNotification();
    IntentFilter filter = new IntentFilter();
    filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
    filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
    registerReceiver(bluetoothReceiver, filter);
    initializeBluetoothProfile();
    initAudioRecord();
    service = this;
}

@RequiresApi(api = Build.VERSION_CODES.O)
private void initNotification() {
    Intent meterIntent = new Intent(this, MainActivity.class);
    meterIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    PendingIntent pendingStartMeterIntent = PendingIntent.getActivity(this, 0, meterIntent, PendingIntent.FLAG_IMMUTABLE);

    Intent stopIntent = new Intent(this, MonitorServiceMic.class);
    stopIntent.setAction("ACTION_STOP_MONITOR");
    PendingIntent pendingStopIntent = PendingIntent.getService(this, 1, stopIntent, PendingIntent.FLAG_IMMUTABLE);

    String initialText = "Using the device mic: " + micSourceName +
            "\nLevel: N/A | Safe For: N/A";

    nCB.setSmallIcon(R.drawable.ic_hearing_24px)
            .setContentTitle("Atlas Safe Listen - Mic Monitoring")
            .setContentText(initialText)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingStartMeterIntent)
            .setAutoCancel(false)
            .addAction(R.drawable.ic_mic_off_24px, "Stop", pendingStopIntent)
            .setOngoing(true)
            .setOnlyAlertOnce(true);

    int serviceType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ?
            ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE : 0;
    try {
        ServiceCompat.startForeground(this, NOTIFICATION_ID, nCB.build(), serviceType);
    } catch (Exception e) {
        Log.e(TAG, "Failed to start foreground service: " + e.getMessage(), e);
        stopSelf();
    }
}

private void createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel channel = new NotificationChannel(
                CHANNEL_ID,
                "Mic Monitoring",
                NotificationManager.IMPORTANCE_DEFAULT
        );
        channel.setDescription("Notifications for mic monitoring service");
        channel.enableVibration(true);
        channel.setSound(null, null);
        nm.createNotificationChannel(channel);
    }
}

@SuppressLint("MissingPermission")
private void initializeBluetoothProfile() {
    if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
        Log.d(TAG, "Bluetooth not enabled or adapter unavailable, using device mic: " + micSourceName);
        isBluetoothMic = false;
        connectedBluetoothDevice = null;
        return;
    }

    bluetoothAdapter.getProfileProxy(this, new BluetoothProfile.ServiceListener() {
        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy) {
            if (profile == BluetoothProfile.HEADSET) {
                bluetoothHeadset = (BluetoothHeadset) proxy;
                checkBluetoothMic();
            }
        }

        @Override
        public void onServiceDisconnected(int profile) {
            if (profile == BluetoothProfile.HEADSET) {
                bluetoothHeadset = null;
                Log.d(TAG, "HFP profile disconnected");
                isBluetoothMic = false;
                micSourceName = getDeviceName();
                connectedBluetoothDevice = null;
                reinitializeAudioRecord();
            }
        }
    }, BluetoothProfile.HEADSET);
}

@SuppressLint("MissingPermission")
private void checkBluetoothMic() {
    if (isInitializing) {
        Log.d(TAG, "AudioRecord initialization in progress, skipping Bluetooth mic check");
        return;
    }

    if (bluetoothHeadset == null || !bluetoothAdapter.isEnabled()) {
        Log.d(TAG, "No Bluetooth headset or Bluetooth disabled, using device mic: " + micSourceName);
        if (isBluetoothMic || connectedBluetoothDevice != null) {
            isBluetoothMic = false;
            connectedBluetoothDevice = null;
            micSourceName = getDeviceName();
            reinitializeAudioRecord();
        }
        return;
    }

    boolean foundConnectedDevice = false;
    for (BluetoothDevice device : bluetoothHeadset.getConnectedDevices()) {
        if (device != null) {
            connectedBluetoothDevice = device;
            micSourceName = device.getName() != null ? device.getName() : "Bluetooth Device";
            isBluetoothMic = true;
            foundConnectedDevice = true;
            Log.d(TAG, "Detected connected HFP device: " + micSourceName);
            reinitializeAudioRecord();
            break;
        }
    }

    if (!foundConnectedDevice && (isBluetoothMic || connectedBluetoothDevice != null)) {
        Log.d(TAG, "No HFP devices connected, switching to device mic: " + micSourceName);
        isBluetoothMic = false;
        connectedBluetoothDevice = null;
        micSourceName = getDeviceName();
        reinitializeAudioRecord();
    }
}

private void initAudioRecord() {
    if (isInitializing) {
        Log.d(TAG, "AudioRecord initialization in progress, skipping");
        return;
    }
    isInitializing = true;
    Log.d(TAG, "Initializing AudioRecord...");
    releaseAudioRecord();

    try {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            Log.e(TAG, "RECORD_AUDIO permission not granted");
            isInitializing = false;
            stopSelf();
            return;
        }

        if (isBluetoothMic && audioManager != null && connectedBluetoothDevice != null && bluetoothAdapter.isEnabled()) {
            Log.d(TAG, "Attempting Bluetooth mic setup: " + micSourceName);
            startBluetoothRecording();
            return;
        }

        setupAudioRecord(android.media.MediaRecorder.AudioSource.MIC);
        isInitializing = false;
    } catch (Exception e) {
        Log.e(TAG, "AudioRecord initialization failed: " + e.getMessage(), e);
        isRecording = false;
        isInitializing = false;
        updateNotificationWithWarning("AudioRecord initialization failed: " + micSourceName);
        stopSelf();
    }
}

private void startBluetoothRecording() {
    final int[] count = {0};
    audioManager.setMode(AudioManager.MODE_NORMAL);
    Log.d(TAG, "AudioManager mode before SCO: " + audioManager.getMode() +
            ", isBluetoothScoAvailable: " + audioManager.isBluetoothScoAvailableOffCall());

    if (!audioManager.isBluetoothScoAvailableOffCall()) {
        Log.w(TAG, "Bluetooth SCO not available, falling back to device mic");
        isBluetoothMic = false;
        micSourceName = getDeviceName();
        connectedBluetoothDevice = null;
        setupAudioRecord(android.media.MediaRecorder.AudioSource.MIC);
        isInitializing = false;
        return;
    }

    final CountDownTimer timer = new CountDownTimer(SCO_TIMEOUT_MS, SCO_COUNTDOWN_INTERVAL_MS) {
        @Override
        public void onTick(long millisUntilFinished) {
            // Do nothing
        }

        @Override
        public void onFinish() {
            Log.w(TAG, "SCO connection timed out, falling back to device mic");
            if (scoReceiver != null) {
                try {
                    unregisterReceiver(scoReceiver);
                } catch (IllegalArgumentException e) {
                    Log.e(TAG, "SCO receiver not registered: " + e.getMessage());
                }
            }
            audioManager.stopBluetoothSco();
            isBluetoothMic = false;
            micSourceName = getDeviceName();
            connectedBluetoothDevice = null;
            setupAudioRecord(android.media.MediaRecorder.AudioSource.MIC);
            isInitializing = false;
        }
    };

    scoReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
            if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
                Log.d(TAG, "SCO connected, starting Bluetooth recording");
                timer.cancel();
                try {
                    unregisterReceiver(this);
                } catch (IllegalArgumentException e) {
                    Log.e(TAG, "SCO receiver not registered: " + e.getMessage());
                }
                audioManager.setBluetoothScoOn(true);
                setupAudioRecord(android.media.MediaRecorder.AudioSource.VOICE_COMMUNICATION);
                isInitializing = false;
            } else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) {
                count[0]++;
                if (count[0] >= MAX_SCO_ATTEMPTS) {
                    Log.w(TAG, "SCO failed after " + MAX_SCO_ATTEMPTS + " attempts, falling back to device mic");
                    timer.cancel();
                    try {
                        unregisterReceiver(this);
                    } catch (IllegalArgumentException e) {
                        Log.e(TAG, "SCO receiver not registered: " + e.getMessage());
                    }
                    audioManager.stopBluetoothSco();
                    isBluetoothMic = false;
                    micSourceName = getDeviceName();
                    connectedBluetoothDevice = null;
                    setupAudioRecord(android.media.MediaRecorder.AudioSource.MIC);
                    isInitializing = false;
                } else {
                    Log.d(TAG, "SCO disconnected, retrying (" + count[0] + "/" + MAX_SCO_ATTEMPTS + ")");
                    audioManager.startBluetoothSco();
                }
            }
        }
    };

    IntentFilter scoFilter = new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
    registerReceiver(scoReceiver, scoFilter);
    timer.start();
    audioManager.startBluetoothSco();
}

private void setupAudioRecord(int audioSource) {
    try {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            return;
        }
        audioRecord = new AudioRecord(audioSource, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, BUFFER_SIZE);
        if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
            Log.e(TAG, "AudioRecord not initialized");
            releaseAudioRecord();
            if (isBluetoothMic) {
                isBluetoothMic = false;
                micSourceName = getDeviceName();
                connectedBluetoothDevice = null;
                updateNotificationWithWarning("Bluetooth mic not supported, falling back to device mic: " + micSourceName);
                setupAudioRecord(android.media.MediaRecorder.AudioSource.MIC);
            } else {
                stopSelf();
            }
            return;
        }

        audioRecord.startRecording();
        isRecording = true;
        Log.d(TAG, "AudioRecord started successfully with " +
                (isBluetoothMic ? "Bluetooth mic: " + micSourceName : "device mic: " + micSourceName));
        updateNotification();
        startMonitoring();
    } catch (Exception e) {
        Log.e(TAG, "AudioRecord setup failed: " + e.getMessage(), e);
        releaseAudioRecord();
        if (isBluetoothMic) {
            isBluetoothMic = false;
            micSourceName = getDeviceName();
            connectedBluetoothDevice = null;
            updateNotificationWithWarning("Bluetooth mic not supported, falling back to device mic: " + micSourceName);
            setupAudioRecord(android.media.MediaRecorder.AudioSource.MIC);
        } else {
            stopSelf();
        }
    }
}

private void handleBluetoothReconnect() {
    if (isRecording) {
        stopMonitoring();
    }
    micUpdateHandler.postDelayed(() -> {
        if (isBluetoothMic && connectedBluetoothDevice != null && bluetoothHeadset != null &&
                bluetoothHeadset.getConnectedDevices().contains(connectedBluetoothDevice)) {
            Log.d(TAG, "Reinitializing AudioRecord after reconnect delay");
            reinitializeAudioRecord();
        } else {
            Log.w(TAG, "Bluetooth device no longer connected after delay, staying with device mic");
            isBluetoothMic = false;
            micSourceName = getDeviceName();
            connectedBluetoothDevice = null;
            reinitializeAudioRecord();
        }
    }, RECONNECT_DELAY_MS);
}

private void reinitializeAudioRecord() {
    if (isInitializing) {
        Log.d(TAG, "AudioRecord initialization in progress, skipping reinitialization");
        return;
    }
    Log.d(TAG, "Reinitializing AudioRecord due to mic source change");
    stopMonitoring();
    initAudioRecord();
}

private void updateNotification() {
    int roundedDecibel = (lastDecibel > 0) ? Math.round((float) lastDecibel) : 0;
    String text = (isBluetoothMic ?
            "Using Bluetooth Mic: " + micSourceName :
            "Using the device mic: " + micSourceName) +
            "\nLevel: " + (lastDecibel > 0 ? roundedDecibel + " dB" : "N/A") +
            " | Safe For: " + lastSafeFor;
    nCB.setContentText(text)
            .setOnlyAlertOnce(true);
    nm.notify(NOTIFICATION_ID, nCB.build());
    Log.d(TAG, "Notification updated: " + text);
}

private void updateNotificationWithWarning(String warning) {
    int roundedDecibel = (lastDecibel > 0) ? Math.round((float) lastDecibel) : 0;
    String text = warning +
            "\nLevel: " + (lastDecibel > 0 ? roundedDecibel + " dB" : "N/A") +
            " | Safe For: " + lastSafeFor;
    nCB.setContentText(text)
            .setOnlyAlertOnce(true);
    nm.notify(NOTIFICATION_ID, nCB.build());
    Log.d(TAG, "Notification updated with warning: " + text);
}

private void startMonitoring() {
    Log.d(TAG, "Starting monitoring...");
    if (isRecording && audioRecord != null) {
        micUpdateHandler.post(micUpdateRunnable);
    } else {
        Log.w(TAG, "Cannot start monitoring: AudioRecord not initialized");
        if (!isInitializing) {
            initAudioRecord();
        }
    }
}

private void stopMonitoring() {
    if (micUpdateHandler != null) {
        micUpdateHandler.removeCallbacks(micUpdateRunnable);
    }
    if (isRecording && audioRecord != null) {
        try {
            audioRecord.stop();
            isRecording = false;
            Log.d(TAG, "AudioRecord stopped successfully");
        } catch (IllegalStateException e) {
            Log.e(TAG, "Error stopping AudioRecord: " + e.getMessage());
            isRecording = false;
        }
    }
}

public void updateMicStatus() {
    if (isRecording && audioRecord != null) {
        try {
            short[] buffer = new short[BUFFER_SIZE / 2];
            int read = audioRecord.read(buffer, 0, buffer.length);
            if (read > 0) {
                // Find max amplitude, similar to MediaRecorder.getMaxAmplitude()
                double maxAmplitude = 0;
                for (short sample : buffer) {
                    double absSample = Math.abs(sample);
                    if (absSample > maxAmplitude) {
                        maxAmplitude = absSample;
                    }
                }
                // Calculate dB with calibration, inspired by MediaRecorder logic
                double decibel = maxAmplitude > 0 ? 20 * Math.log10(maxAmplitude / BASE) + 90 : 0;
                if (decibel > 30 && decibel < 120 && Double.isFinite(decibel)) { // Realistic range
                    lastDecibel = decibel;
                    volume.postValue((float) decibel);
                    lastSafeFor = getSafeFor(decibel);

                    int roundedDecibel = Math.round((float) decibel);
                    boolean shouldUpdateNotification = Math.abs(roundedDecibel - lastNotifiedDecibel) >= 1 ||
                            !lastSafeFor.equals(lastNotifiedSafeFor);

                    if (shouldUpdateNotification) {
                        lastNotifiedDecibel = roundedDecibel;
                        lastNotifiedSafeFor = lastSafeFor;
                        updateNotification();
                        Log.d(TAG, "Mic Volume Updated: " + decibel + " dB, Safe For: " + lastSafeFor +
                                ", Source: " + (isBluetoothMic ? "Bluetooth (" + micSourceName + ")" : "Device (" + micSourceName + ")"));
                    }
                } else {
                    Log.w(TAG, "Invalid decibel value: " + decibel + ", maxAmplitude: " + maxAmplitude);
                }
            } else {
                Log.w(TAG, "No audio data read: " + read);
            }
        } catch (Exception e) {
            Log.e(TAG, "Error reading audio data: " + e.getMessage(), e);
            isRecording = false;
            if (!isInitializing) {
                reinitializeAudioRecord();
            }
        }
    }
}

private String getSafeFor(double vol) {
    if (vol <= 85) {
        return "24h"; // Safe for extended periods
    }
    // NIOSH formula: Time (hours) = 8 / 2^((dB - 85)/3)
    double hours = 8.0 / Math.pow(2.0, (vol - 85.0) / 3.0);
    if (hours < 0.01) {
        return "<1m"; // Less than a minute
    }
    if (hours < 1) {
        int minutes = (int) Math.round(hours * 60);
        return minutes + "m";
    }
    int roundedHours = (int) Math.round(hours);
    return roundedHours + "h";
}

@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onDestroy() {
    super.onDestroy();
    Log.d(TAG, "Destroying service...");
    stopMonitoring();
    releaseAudioRecord();
    if (audioManager != null) {
        audioManager.stopBluetoothSco();
        audioManager.setBluetoothScoOn(false);
    }
    if (bluetoothHeadset != null && bluetoothAdapter != null) {
        bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset);
    }
    if (scoReceiver != null) {
        try {
            unregisterReceiver(scoReceiver);
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "SCO receiver not registered: " + e.getMessage());
        }
    }
    try {
        unregisterReceiver(bluetoothReceiver);
    } catch (IllegalArgumentException e) {
        Log.e(TAG, "Receiver not registered: " + e.getMessage());
    }
    if (micUpdateHandler != null) {
        micUpdateHandler.removeCallbacksAndMessages(null);
    }
    ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE);
    service = null;
    Log.d(TAG, "Service destroyed");
}

private void releaseAudioRecord() {
    if (audioRecord != null) {
        try {
            if (isRecording) {
                audioRecord.stop();
                isRecording = false;
            }
        } catch (IllegalStateException e) {
            Log.e(TAG, "Error stopping AudioRecord: " + e.getMessage());
        } finally {
            audioRecord.release();
            audioRecord = null;
            isRecording = false;
            Log.d(TAG, "AudioRecord released");
        }
    }
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (intent != null && "ACTION_STOP_MONITOR".equals(intent.getAction())) {
        Log.d(TAG, "Received stop action, stopping service");
        stopForeground(true);
        stopSelf();
        return START_NOT_STICKY;
    }
    Log.d(TAG, "Service started or restarted");
    if (!isRecording && !isInitializing) {
        initAudioRecord();
    }
    return START_STICKY;
}

public MutableLiveData<Float> getVolume() {
    return volume;
}

private String getDeviceName() {
    String manufacturer = Build.MANUFACTURER;
    String model = Build.MODEL;
    if (model.startsWith(manufacturer)) {
        return capitalize(model);
    } else {
        return capitalize(manufacturer) + " " + model;
    }
}

private String capitalize(String s) {
    if (s == null || s.isEmpty()) {
        return s;
    }
    return s.substring(0, 1).toUpperCase() + s.substring(1);
}

@Override
public IBinder onBind(Intent intent) {
    return binderMic;
}

public class LocalBinder extends Binder {
    public MonitorServiceMic getService() {
        return MonitorServiceMic.this;
    }
}

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment