From e17172b4d3f4c5c54fabed2cc14c5a18eaa1b002 Mon Sep 17 00:00:00 2001 From: Shubham Chakraborty Date: Sun, 22 Mar 2026 21:47:17 +0530 Subject: [PATCH] refactor: Extract audio logic into AudioController Separate audio management logic from UI code for better maintainability and single responsibility principle. Changes: - Create AudioController.kt to encapsulate all AudioManager and LoudnessEnhancer logic - Move audio methods from ActivityMain to AudioController: - setInitialVolume() - applyBoost(level: Int) - applyVolume(level: Int) - restartAudioPlayback() - getOutputDeviceInfo() - release() - Update ActivityMain to delegate audio operations to AudioController - Preserve all existing logic, formatting, and behavior Signed-off-by: Shubham Chakraborty --- .../java/com/example/boostx/ActivityMain.kt | 150 +++--------------- .../com/example/boostx/AudioController.kt | 126 +++++++++++++++ 2 files changed, 149 insertions(+), 127 deletions(-) create mode 100644 app/src/main/java/com/example/boostx/AudioController.kt diff --git a/app/src/main/java/com/example/boostx/ActivityMain.kt b/app/src/main/java/com/example/boostx/ActivityMain.kt index 9ad1ecc..2dc6da6 100644 --- a/app/src/main/java/com/example/boostx/ActivityMain.kt +++ b/app/src/main/java/com/example/boostx/ActivityMain.kt @@ -3,10 +3,6 @@ package com.example.boostx import android.content.res.ColorStateList import android.graphics.Color import android.graphics.Typeface -import android.media.AudioDeviceInfo -import android.media.AudioFormat -import android.media.AudioManager -import android.media.audiofx.LoudnessEnhancer import android.os.Build import android.os.Bundle import android.os.Handler @@ -19,7 +15,6 @@ import android.text.style.ForegroundColorSpan import android.text.style.RelativeSizeSpan import android.text.style.StyleSpan import android.text.style.URLSpan -import android.view.KeyEvent import android.view.View import android.view.ViewGroup import android.widget.LinearLayout @@ -39,25 +34,22 @@ class MainActivity : AppCompatActivity() { private lateinit var volumeTextView: TextView private lateinit var outputDeviceTextView: TextView - private lateinit var audioManager: AudioManager - private var audioSessionID = 0 - private var loudnessEnhancer: LoudnessEnhancer? = null - private var lastDeviceId: Int? = null - private var isBoostEnabled = true + private lateinit var audioController: AudioController private val handler = Handler(Looper.getMainLooper()) private val updateRunnable = object : Runnable { override fun run() { - updateOutputDeviceInfo() + updateOutputDeviceInfoUI() handler.postDelayed(this, 1800) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setTheme(R.style.Theme_BoostX) // Apply dark theme + setTheme(R.style.Theme_BoostX) setContentView(R.layout.activity_main) + audioController = AudioController(this) boostSlider = findViewById(R.id.boostSlider) volumeSlider = findViewById(R.id.volumeSlider) @@ -79,13 +71,7 @@ class MainActivity : AppCompatActivity() { handleGradualBoostSwitch(isChecked, originalBoostSliderProperties) } - - loudnessEnhancer = LoudnessEnhancer(0) - audioManager = getSystemService(AUDIO_SERVICE) as AudioManager - audioSessionID = audioManager.generateAudioSessionId() - - val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) - audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, maxVolume, 0) + audioController.setInitialVolume() volumeSlider.value = 100f boostTextView = findViewById(R.id.boostLevel) @@ -93,12 +79,11 @@ class MainActivity : AppCompatActivity() { outputDeviceTextView = findViewById(R.id.outputDeviceText) boostSlider.addOnChangeListener { _, value, _ -> applyBoost(value.toInt()) } - volumeSlider.addOnChangeListener { _, value, _ -> applyVolume(value.toInt(), maxVolume) } + volumeSlider.addOnChangeListener { _, value, _ -> applyVolume(value.toInt()) } findViewById(R.id.infoIcon).setOnClickListener { showAppInfo() } - } private fun showAppInfo(){ @@ -133,7 +118,7 @@ class MainActivity : AppCompatActivity() { infoBuilder.append("\n\n") val noteText = SpannableString( - "Session ID:\t\t$audioSessionID\n" + + "Session ID:\t\t${audioController.audioSessionID}\n" + "Package:\t\t\t\t\t\t$packageName\n\n" + "Warning: Excessive boost may distort audio or harm speakers." ) @@ -142,40 +127,37 @@ class MainActivity : AppCompatActivity() { infoBuilder.append(noteText) - // Creating TextView for Dialog val textView = TextView(this) textView.text = infoBuilder textView.setBackgroundColor("#202020".toColorInt()) - textView.setTextColor(Color.WHITE) // Ensuring text is visible on dark background + textView.setTextColor(Color.WHITE) textView.textSize = 16f textView.movementMethod = LinkMovementMethod.getInstance() textView.setPadding(48, 48, 48, 48) val spacer = View(this) val spacerParams = LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, // Match width - 50 // Height of spacer in pixels (adjust as needed) + ViewGroup.LayoutParams.MATCH_PARENT, + 50 ) spacer.layoutParams = spacerParams val linearLayout = LinearLayout(this) linearLayout.orientation = LinearLayout.VERTICAL - linearLayout.setBackgroundColor("#CCFF00".toColorInt()) // Neon background - linearLayout.addView(spacer) // Add Spacer on Top - linearLayout.addView(textView) // Add TextView Below + linearLayout.setBackgroundColor("#CCFF00".toColorInt()) + linearLayout.addView(spacer) + linearLayout.addView(textView) val dialog = infoDialog.setView(linearLayout) .create() dialog.show() - } private fun handleGradualBoostSwitch(isChecked:Boolean, originalBoostSliderProperties:MutableMap){ if (isChecked) { gradualBoostSwitch.thumbTintList = ColorStateList.valueOf("#CCFF00".toColorInt()) gradualBoostSwitch.trackTintList = ColorStateList.valueOf("#666600".toColorInt()) - gradualBoostSwitch.setTextColor(Color.WHITE) boostSlider.valueFrom = volumeSlider.valueFrom @@ -192,7 +174,6 @@ class MainActivity : AppCompatActivity() { } else { gradualBoostSwitch.thumbTintList = ColorStateList.valueOf(Color.GRAY) gradualBoostSwitch.trackTintList = ColorStateList.valueOf(Color.DKGRAY) - gradualBoostSwitch.setTextColor(Color.GRAY) boostSlider.value = (boostSlider.value / 10).roundToInt() * 10f @@ -209,124 +190,39 @@ class MainActivity : AppCompatActivity() { boostSlider.isTickVisible = originalBoostSliderProperties["isTickVisible"] as Boolean } } - private fun restartAudioPlayback() { - isBoostEnabled = false - audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE)) - audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PAUSE)) - audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY)) - audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY)) - audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE)) - audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PAUSE)) - audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY)) - audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY)) - } - private fun applyBoost(level: Int) { boostTextView.text = "$level%" boostTextView.setTextColor(if (level > 50) "#F92672".toColorInt() else Color.GRAY) - - loudnessEnhancer?.setTargetGain(level * 25) - loudnessEnhancer?.enabled = true - - if (isBoostEnabled) restartAudioPlayback() + audioController.applyBoost(level) } - private fun applyVolume(level: Int, maxVolume: Int) { + private fun applyVolume(level: Int) { volumeTextView.text = "$level%" - audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, - ((level.toFloat() / 100) * maxVolume).toInt(), - 0) + audioController.applyVolume(level) } - private fun updateOutputDeviceInfo() { - val devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS) - - // Prioritize Bluetooth and Wired devices first, then fallback to Speaker - val activeDevice = devices.firstOrNull { isActiveOutputDevice(it) } - ?: devices.firstOrNull { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER } - - // If no active device found - if (activeDevice == null) { - if (lastDeviceId != null) { // Only update UI if previously there was a device - lastDeviceId = null - runOnUiThread { - outputDeviceTextView.text = "No Active Output Device Detected" - } - } - return - } - - // Avoid unnecessary UI updates if the same device is still active - if (activeDevice.id == lastDeviceId) return - lastDeviceId = activeDevice.id - - val sampleRates = activeDevice.sampleRates.joinToString() - val deviceType = activeDevice.type - val info = "Device Name:\t\t\t\t${activeDevice.productName ?: "N/A"}\n" + - "Device Type:\t\t\t\t${getDeviceType(deviceType)} (${deviceType})\n" + - "Device ID:\t\t\t\t\t\t${activeDevice.id}\n\n"+ - "Channels:\t\t\t\t\t\t\t\t${activeDevice.channelCounts.joinToString().ifEmpty { "N/A" }}\n" + - "Encodings:\t\t\t\t\t\t${getEncodingFormat(activeDevice.encodings).ifEmpty { "N/A" }}\n\n"+ - "Sample Rates: ${if (sampleRates.isEmpty()) "\tN/A" else "\n"+sampleRates+"Hz"}\n" - - runOnUiThread { + private fun updateOutputDeviceInfoUI() { + val info = audioController.getOutputDeviceInfo() + if (info != null) { outputDeviceTextView.text = info } } - - private fun isActiveOutputDevice(device: AudioDeviceInfo): Boolean { - return when (device.type) { - AudioDeviceInfo.TYPE_BLUETOOTH_A2DP -> audioManager.isBluetoothA2dpOn - AudioDeviceInfo.TYPE_WIRED_HEADPHONES, AudioDeviceInfo.TYPE_WIRED_HEADSET -> true - AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> audioManager.isSpeakerphoneOn - else -> false - } - } - - private fun getDeviceType(type: Int): String { - return when (type) { - AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "Bluetooth" - AudioDeviceInfo.TYPE_WIRED_HEADPHONES, AudioDeviceInfo.TYPE_WIRED_HEADSET -> "Wired Headphones" - AudioDeviceInfo.TYPE_USB_DEVICE, AudioDeviceInfo.TYPE_USB_HEADSET -> "USB Audio" - AudioDeviceInfo.TYPE_HDMI, AudioDeviceInfo.TYPE_HDMI_ARC -> "HDMI Output" - AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> "Device Speaker" - AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> "Earpiece" - else -> "Unknown Device" - } - } - - private fun getEncodingFormat(formats: IntArray): String { - return formats.joinToString { encodingMap[it] ?: "Unknown Format" } - } - - private val encodingMap = mapOf( - AudioFormat.ENCODING_PCM_16BIT to "PCM 16-bit", - AudioFormat.ENCODING_PCM_8BIT to "PCM 8-bit", - AudioFormat.ENCODING_PCM_FLOAT to "PCM Float", - AudioFormat.ENCODING_AC3 to "Dolby AC3", - AudioFormat.ENCODING_E_AC3 to "Dolby Digital+", - AudioFormat.ENCODING_DTS to "DTS", - AudioFormat.ENCODING_DTS_HD to "DTS-HD", - AudioFormat.ENCODING_AAC_ELD to "AAC ELD", - AudioFormat.ENCODING_AAC_HE_V1 to "AAC HE v1", - AudioFormat.ENCODING_AAC_HE_V2 to "AAC HE v2" - ) - override fun onResume() { super.onResume() - handler.post(updateRunnable) // Start updates when app is active + handler.post(updateRunnable) } override fun onPause() { super.onPause() handler.removeCallbacks(updateRunnable) } + override fun onDestroy() { super.onDestroy() - loudnessEnhancer?.release() + audioController.release() handler.removeCallbacks(updateRunnable) handler.removeCallbacksAndMessages(null) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/boostx/AudioController.kt b/app/src/main/java/com/example/boostx/AudioController.kt new file mode 100644 index 0000000..1bfed91 --- /dev/null +++ b/app/src/main/java/com/example/boostx/AudioController.kt @@ -0,0 +1,126 @@ +package com.example.boostx + +import android.content.Context +import android.media.AudioDeviceInfo +import android.media.AudioFormat +import android.media.AudioManager +import android.media.audiofx.LoudnessEnhancer +import android.view.KeyEvent + +class AudioController(private val context: Context) { + private val audioManager: AudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + private var loudnessEnhancer: LoudnessEnhancer? = null + var audioSessionID: Int = 0 + private set + + private var lastDeviceId: Int? = null + var isBoostEnabled = true + + init { + loudnessEnhancer = LoudnessEnhancer(0) + audioSessionID = audioManager.generateAudioSessionId() + } + + fun getMaxVolume(): Int = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) + + fun setInitialVolume() { + val maxVolume = getMaxVolume() + audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, maxVolume, 0) + } + + fun applyBoost(level: Int) { + loudnessEnhancer?.setTargetGain(level * 25) + loudnessEnhancer?.enabled = true + if (isBoostEnabled) restartAudioPlayback() + } + + fun applyVolume(level: Int) { + val maxVolume = getMaxVolume() + audioManager.setStreamVolume( + AudioManager.STREAM_MUSIC, + ((level.toFloat() / 100) * maxVolume).toInt(), + 0 + ) + } + + fun restartAudioPlayback() { + isBoostEnabled = false + audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE)) + audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PAUSE)) + audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY)) + audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY)) + audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE)) + audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PAUSE)) + audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY)) + audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY)) + } + + fun getOutputDeviceInfo(): String? { + val devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS) + + val activeDevice = devices.firstOrNull { isActiveOutputDevice(it) } + ?: devices.firstOrNull { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER } + + if (activeDevice == null) { + if (lastDeviceId != null) { + lastDeviceId = null + return "No Active Output Device Detected" + } + return null + } + + if (activeDevice.id == lastDeviceId) return null + lastDeviceId = activeDevice.id + + val sampleRates = activeDevice.sampleRates.joinToString() + val deviceType = activeDevice.type + return "Device Name:\t\t\t\t${activeDevice.productName ?: "N/A"}\n" + + "Device Type:\t\t\t\t${getDeviceType(deviceType)} (${deviceType})\n" + + "Device ID:\t\t\t\t\t\t${activeDevice.id}\n\n" + + "Channels:\t\t\t\t\t\t\t\t${activeDevice.channelCounts.joinToString().ifEmpty { "N/A" }}\n" + + "Encodings:\t\t\t\t\t\t${getEncodingFormat(activeDevice.encodings).ifEmpty { "N/A" }}\n\n" + + "Sample Rates: ${if (sampleRates.isEmpty()) "\tN/A" else "\n" + sampleRates + "Hz"}\n" + } + + private fun isActiveOutputDevice(device: AudioDeviceInfo): Boolean { + return when (device.type) { + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP -> audioManager.isBluetoothA2dpOn + AudioDeviceInfo.TYPE_WIRED_HEADPHONES, AudioDeviceInfo.TYPE_WIRED_HEADSET -> true + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> audioManager.isSpeakerphoneOn + else -> false + } + } + + private fun getDeviceType(type: Int): String { + return when (type) { + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "Bluetooth" + AudioDeviceInfo.TYPE_WIRED_HEADPHONES, AudioDeviceInfo.TYPE_WIRED_HEADSET -> "Wired Headphones" + AudioDeviceInfo.TYPE_USB_DEVICE, AudioDeviceInfo.TYPE_USB_HEADSET -> "USB Audio" + AudioDeviceInfo.TYPE_HDMI, AudioDeviceInfo.TYPE_HDMI_ARC -> "HDMI Output" + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> "Device Speaker" + AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> "Earpiece" + else -> "Unknown Device" + } + } + + private fun getEncodingFormat(formats: IntArray): String { + return formats.joinToString { encodingMap[it] ?: "Unknown Format" } + } + + private val encodingMap = mapOf( + AudioFormat.ENCODING_PCM_16BIT to "PCM 16-bit", + AudioFormat.ENCODING_PCM_8BIT to "PCM 8-bit", + AudioFormat.ENCODING_PCM_FLOAT to "PCM Float", + AudioFormat.ENCODING_AC3 to "Dolby AC3", + AudioFormat.ENCODING_E_AC3 to "Dolby Digital+", + AudioFormat.ENCODING_DTS to "DTS", + AudioFormat.ENCODING_DTS_HD to "DTS-HD", + AudioFormat.ENCODING_AAC_ELD to "AAC ELD", + AudioFormat.ENCODING_AAC_HE_V1 to "AAC HE v1", + AudioFormat.ENCODING_AAC_HE_V2 to "AAC HE v2" + ) + + fun release() { + loudnessEnhancer?.release() + } +}