mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-03-17 19:52:31 +00:00
android: separated actual battery notifications from persistent service notif; better error handling when socket isn't connected
This commit is contained in:
@@ -26,7 +26,6 @@ import android.app.NotificationManager
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.appwidget.AppWidgetManager
|
import android.appwidget.AppWidgetManager
|
||||||
import android.bluetooth.BluetoothAssignedNumbers.APPLE
|
|
||||||
import android.bluetooth.BluetoothDevice
|
import android.bluetooth.BluetoothDevice
|
||||||
import android.bluetooth.BluetoothManager
|
import android.bluetooth.BluetoothManager
|
||||||
import android.bluetooth.BluetoothProfile
|
import android.bluetooth.BluetoothProfile
|
||||||
@@ -71,7 +70,9 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
import me.kavishdevar.librepods.MainActivity
|
import me.kavishdevar.librepods.MainActivity
|
||||||
import me.kavishdevar.librepods.R
|
import me.kavishdevar.librepods.R
|
||||||
import me.kavishdevar.librepods.utils.AirPodsNotifications
|
import me.kavishdevar.librepods.utils.AirPodsNotifications
|
||||||
@@ -88,12 +89,10 @@ import me.kavishdevar.librepods.utils.IslandWindow
|
|||||||
import me.kavishdevar.librepods.utils.LongPressPackets
|
import me.kavishdevar.librepods.utils.LongPressPackets
|
||||||
import me.kavishdevar.librepods.utils.MediaController
|
import me.kavishdevar.librepods.utils.MediaController
|
||||||
import me.kavishdevar.librepods.utils.PopupWindow
|
import me.kavishdevar.librepods.utils.PopupWindow
|
||||||
import me.kavishdevar.librepods.utils.RadareOffsetFinder
|
|
||||||
import me.kavishdevar.librepods.utils.SystemApisUtils
|
import me.kavishdevar.librepods.utils.SystemApisUtils
|
||||||
import me.kavishdevar.librepods.utils.SystemApisUtils.DEVICE_TYPE_UNTETHERED_HEADSET
|
import me.kavishdevar.librepods.utils.SystemApisUtils.DEVICE_TYPE_UNTETHERED_HEADSET
|
||||||
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_COMPANION_APP
|
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_COMPANION_APP
|
||||||
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_DEVICE_TYPE
|
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_DEVICE_TYPE
|
||||||
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_MAIN_BATTERY
|
|
||||||
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_MAIN_ICON
|
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_MAIN_ICON
|
||||||
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_MANUFACTURER_NAME
|
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_MANUFACTURER_NAME
|
||||||
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_MODEL_NAME
|
import me.kavishdevar.librepods.utils.SystemApisUtils.METADATA_MODEL_NAME
|
||||||
@@ -197,10 +196,10 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
|
|
||||||
inMemoryLogs.addAll(sharedPreferencesLogs.getStringSet(packetLogKey, emptySet()) ?: emptySet())
|
inMemoryLogs.addAll(sharedPreferencesLogs.getStringSet(packetLogKey, emptySet()) ?: emptySet())
|
||||||
_packetLogsFlow.value = inMemoryLogs.toSet()
|
_packetLogsFlow.value = inMemoryLogs.toSet()
|
||||||
|
|
||||||
sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE)
|
sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
initializeConfig()
|
initializeConfig()
|
||||||
|
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +232,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
|
|
||||||
override fun onSharedPreferenceChanged(preferences: SharedPreferences?, key: String?) {
|
override fun onSharedPreferenceChanged(preferences: SharedPreferences?, key: String?) {
|
||||||
if (preferences == null || key == null) return
|
if (preferences == null || key == null) return
|
||||||
|
|
||||||
when(key) {
|
when(key) {
|
||||||
"name" -> config.deviceName = preferences.getString(key, "AirPods") ?: "AirPods"
|
"name" -> config.deviceName = preferences.getString(key, "AirPods") ?: "AirPods"
|
||||||
"automatic_ear_detection" -> config.earDetectionEnabled = preferences.getBoolean(key, true)
|
"automatic_ear_detection" -> config.earDetectionEnabled = preferences.getBoolean(key, true)
|
||||||
@@ -265,7 +264,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
"textColor" -> config.textColor = preferences.getLong(key, -1L)
|
"textColor" -> config.textColor = preferences.getLong(key, -1L)
|
||||||
"qs_click_behavior" -> config.qsClickBehavior = preferences.getString(key, "cycle") ?: "cycle"
|
"qs_click_behavior" -> config.qsClickBehavior = preferences.getString(key, "cycle") ?: "cycle"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key == "mac_address") {
|
if (key == "mac_address") {
|
||||||
macAddress = preferences.getString(key, "") ?: ""
|
macAddress = preferences.getString(key, "") ?: ""
|
||||||
}
|
}
|
||||||
@@ -398,7 +397,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
if (bluetoothDevice.uuids != null) {
|
if (bluetoothDevice.uuids != null) {
|
||||||
if (bluetoothDevice.uuids.contains(uuid)) {
|
if (bluetoothDevice.uuids.contains(uuid)) {
|
||||||
val intent =
|
val intent =
|
||||||
Intent(AirPodsNotifications.Companion.AIRPODS_CONNECTION_DETECTED)
|
Intent(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED)
|
||||||
intent.putExtra("name", name)
|
intent.putExtra("name", name)
|
||||||
intent.putExtra("device", bluetoothDevice)
|
intent.putExtra("device", bluetoothDevice)
|
||||||
context?.sendBroadcast(intent)
|
context?.sendBroadcast(intent)
|
||||||
@@ -436,13 +435,33 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
fun startForegroundNotification() {
|
fun startForegroundNotification() {
|
||||||
val notificationChannel = NotificationChannel(
|
val disconnectedNotificationChannel = NotificationChannel(
|
||||||
"background_service_status",
|
"background_service_status",
|
||||||
"Background Service Status",
|
"Background Service Status",
|
||||||
NotificationManager.IMPORTANCE_LOW
|
NotificationManager.IMPORTANCE_LOW
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val connectedNotificationChannel = NotificationChannel(
|
||||||
|
"airpods_connection_status",
|
||||||
|
"AirPods Connection Status",
|
||||||
|
NotificationManager.IMPORTANCE_LOW,
|
||||||
|
)
|
||||||
|
|
||||||
|
val socketFailureChannel = NotificationChannel(
|
||||||
|
"socket_connection_failure",
|
||||||
|
"AirPods Socket Connection Issues",
|
||||||
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
|
).apply {
|
||||||
|
description = "Notifications about problems connecting to AirPods protocol"
|
||||||
|
enableLights(true)
|
||||||
|
lightColor = android.graphics.Color.RED
|
||||||
|
enableVibration(true)
|
||||||
|
}
|
||||||
|
|
||||||
val notificationManager = getSystemService(NotificationManager::class.java)
|
val notificationManager = getSystemService(NotificationManager::class.java)
|
||||||
notificationManager.createNotificationChannel(notificationChannel)
|
notificationManager.createNotificationChannel(disconnectedNotificationChannel)
|
||||||
|
notificationManager.createNotificationChannel(connectedNotificationChannel)
|
||||||
|
notificationManager.createNotificationChannel(socketFailureChannel)
|
||||||
|
|
||||||
val notificationIntent = Intent(this, MainActivity::class.java)
|
val notificationIntent = Intent(this, MainActivity::class.java)
|
||||||
val pendingIntent = PendingIntent.getActivity(
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
@@ -452,11 +471,22 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val notificationSettingsIntent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply {
|
||||||
|
putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
|
||||||
|
putExtra(Settings.EXTRA_CHANNEL_ID, "background_service_status")
|
||||||
|
}
|
||||||
|
val pendingIntentNotifDisable = PendingIntent.getActivity(
|
||||||
|
this,
|
||||||
|
0,
|
||||||
|
notificationSettingsIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
val notification = NotificationCompat.Builder(this, "background_service_status")
|
val notification = NotificationCompat.Builder(this, "background_service_status")
|
||||||
.setSmallIcon(R.drawable.airpods)
|
.setSmallIcon(R.drawable.airpods)
|
||||||
.setContentTitle("AirPods not connected")
|
.setContentTitle("Background Service Running")
|
||||||
.setContentText("Tap to open app")
|
.setContentText("Useless notification, disable it by clicking on it.")
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntentNotifDisable)
|
||||||
.setCategory(Notification.CATEGORY_SERVICE)
|
.setCategory(Notification.CATEGORY_SERVICE)
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
@@ -469,15 +499,42 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
private fun showSocketConnectionFailureNotification(errorMessage: String) {
|
||||||
|
val notificationManager = getSystemService(NotificationManager::class.java)
|
||||||
|
|
||||||
|
val notificationIntent = Intent(this, MainActivity::class.java)
|
||||||
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
|
this,
|
||||||
|
0,
|
||||||
|
notificationIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
val notification = NotificationCompat.Builder(this, "socket_connection_failure")
|
||||||
|
.setSmallIcon(R.drawable.airpods)
|
||||||
|
.setContentTitle("AirPods Connection Issue")
|
||||||
|
.setContentText("Unable to connect to AirPods over L2CAP")
|
||||||
|
.setStyle(NotificationCompat.BigTextStyle()
|
||||||
|
.bigText("Your AirPods are connected via Bluetooth, but LibrePods couldn't connect to AirPods using L2CAP. " +
|
||||||
|
"Error: $errorMessage"))
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setCategory(Notification.CATEGORY_ERROR)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
notificationManager.notify(3, notification)
|
||||||
|
}
|
||||||
|
|
||||||
fun sendANCBroadcast() {
|
fun sendANCBroadcast() {
|
||||||
sendBroadcast(Intent(AirPodsNotifications.Companion.ANC_DATA).apply {
|
sendBroadcast(Intent(AirPodsNotifications.ANC_DATA).apply {
|
||||||
putExtra("data", ancNotification.status)
|
putExtra("data", ancNotification.status)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendBatteryBroadcast() {
|
fun sendBatteryBroadcast() {
|
||||||
sendBroadcast(Intent(AirPodsNotifications.Companion.BATTERY_DATA).apply {
|
sendBroadcast(Intent(AirPodsNotifications.BATTERY_DATA).apply {
|
||||||
putParcelableArrayListExtra("data", ArrayList(batteryNotification.getBattery()))
|
putParcelableArrayListExtra("data", ArrayList(batteryNotification.getBattery()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -694,8 +751,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
|
|
||||||
if (connected) {
|
if (connected && socket.isConnected) {
|
||||||
updatedNotification = NotificationCompat.Builder(this, "background_service_status")
|
updatedNotification = NotificationCompat.Builder(this, "airpods_connection_status")
|
||||||
.setSmallIcon(R.drawable.airpods)
|
.setSmallIcon(R.drawable.airpods)
|
||||||
.setContentTitle(airpodsName ?: config.deviceName)
|
.setContentTitle(airpodsName ?: config.deviceName)
|
||||||
.setContentText(
|
.setContentText(
|
||||||
@@ -725,11 +782,15 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
} ?: ""
|
} ?: ""
|
||||||
}""")
|
}""")
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.setCategory(Notification.CATEGORY_SERVICE)
|
.setCategory(Notification.CATEGORY_STATUS)
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.build()
|
.build()
|
||||||
} else {
|
|
||||||
|
notificationManager.notify(2, updatedNotification)
|
||||||
|
|
||||||
|
notificationManager.cancel(1)
|
||||||
|
} else if (!connected) {
|
||||||
updatedNotification = NotificationCompat.Builder(this, "background_service_status")
|
updatedNotification = NotificationCompat.Builder(this, "background_service_status")
|
||||||
.setSmallIcon(R.drawable.airpods)
|
.setSmallIcon(R.drawable.airpods)
|
||||||
.setContentTitle("AirPods not connected")
|
.setContentTitle("AirPods not connected")
|
||||||
@@ -739,9 +800,12 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.build()
|
.build()
|
||||||
}
|
|
||||||
|
|
||||||
notificationManager.notify(1, updatedNotification)
|
notificationManager.notify(1, updatedNotification)
|
||||||
|
notificationManager.cancel(2)
|
||||||
|
} else {
|
||||||
|
showSocketConnectionFailureNotification("Socket created, but not connected. Is the Bluetooth process hooked?")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.Q)
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
@@ -1010,10 +1074,10 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
initGestureDetector()
|
initGestureDetector()
|
||||||
|
|
||||||
sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE)
|
sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
|
||||||
with(sharedPreferences) {
|
with(sharedPreferences) {
|
||||||
val editor = edit()
|
val editor = edit()
|
||||||
|
|
||||||
if (!contains("conversational_awareness_pause_music")) editor.putBoolean("conversational_awareness_pause_music", false)
|
if (!contains("conversational_awareness_pause_music")) editor.putBoolean("conversational_awareness_pause_music", false)
|
||||||
if (!contains("personalized_volume")) editor.putBoolean("personalized_volume", false)
|
if (!contains("personalized_volume")) editor.putBoolean("personalized_volume", false)
|
||||||
if (!contains("automatic_ear_detection")) editor.putBoolean("automatic_ear_detection", true)
|
if (!contains("automatic_ear_detection")) editor.putBoolean("automatic_ear_detection", true)
|
||||||
@@ -1030,21 +1094,21 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
if (!contains("volume_control")) editor.putBoolean("volume_control", true)
|
if (!contains("volume_control")) editor.putBoolean("volume_control", true)
|
||||||
if (!contains("head_gestures")) editor.putBoolean("head_gestures", true)
|
if (!contains("head_gestures")) editor.putBoolean("head_gestures", true)
|
||||||
if (!contains("disconnect_when_not_wearing")) editor.putBoolean("disconnect_when_not_wearing", false)
|
if (!contains("disconnect_when_not_wearing")) editor.putBoolean("disconnect_when_not_wearing", false)
|
||||||
|
|
||||||
if (!contains("adaptive_strength")) editor.putInt("adaptive_strength", 51)
|
if (!contains("adaptive_strength")) editor.putInt("adaptive_strength", 51)
|
||||||
if (!contains("tone_volume")) editor.putInt("tone_volume", 75)
|
if (!contains("tone_volume")) editor.putInt("tone_volume", 75)
|
||||||
if (!contains("conversational_awareness_volume")) editor.putInt("conversational_awareness_volume", 43)
|
if (!contains("conversational_awareness_volume")) editor.putInt("conversational_awareness_volume", 43)
|
||||||
|
|
||||||
if (!contains("textColor")) editor.putLong("textColor", -1L)
|
if (!contains("textColor")) editor.putLong("textColor", -1L)
|
||||||
|
|
||||||
if (!contains("qs_click_behavior")) editor.putString("qs_click_behavior", "cycle")
|
if (!contains("qs_click_behavior")) editor.putString("qs_click_behavior", "cycle")
|
||||||
if (!contains("name")) editor.putString("name", "AirPods")
|
if (!contains("name")) editor.putString("name", "AirPods")
|
||||||
|
|
||||||
editor.apply()
|
editor.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeConfig()
|
initializeConfig()
|
||||||
|
|
||||||
ancModeReceiver = object : BroadcastReceiver() {
|
ancModeReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
if (intent?.action == "me.kavishdevar.librepods.SET_ANC_MODE") {
|
if (intent?.action == "me.kavishdevar.librepods.SET_ANC_MODE") {
|
||||||
@@ -1112,14 +1176,19 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
super.onCallStateChanged(state, phoneNumber)
|
super.onCallStateChanged(state, phoneNumber)
|
||||||
when (state) {
|
when (state) {
|
||||||
TelephonyManager.CALL_STATE_RINGING -> {
|
TelephonyManager.CALL_STATE_RINGING -> {
|
||||||
if (CrossDevice.isAvailable && !isConnectedLocally && earDetectionNotification.status.contains(0x00)) takeOver()
|
if (CrossDevice.isAvailable && !isConnectedLocally && earDetectionNotification.status.contains(0x00)) CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
takeOver()
|
||||||
|
}
|
||||||
if (config.headGestures) {
|
if (config.headGestures) {
|
||||||
callNumber = phoneNumber
|
callNumber = phoneNumber
|
||||||
handleIncomingCall()
|
handleIncomingCall()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TelephonyManager.CALL_STATE_OFFHOOK -> {
|
TelephonyManager.CALL_STATE_OFFHOOK -> {
|
||||||
if (CrossDevice.isAvailable && !isConnectedLocally && earDetectionNotification.status.contains(0x00)) takeOver()
|
if (CrossDevice.isAvailable && !isConnectedLocally && earDetectionNotification.status.contains(0x00)) CoroutineScope(
|
||||||
|
Dispatchers.IO).launch {
|
||||||
|
takeOver()
|
||||||
|
}
|
||||||
isInCall = true
|
isInCall = true
|
||||||
}
|
}
|
||||||
TelephonyManager.CALL_STATE_IDLE -> {
|
TelephonyManager.CALL_STATE_IDLE -> {
|
||||||
@@ -1161,23 +1230,25 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
|
|
||||||
connectionReceiver = object : BroadcastReceiver() {
|
connectionReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
if (intent?.action == AirPodsNotifications.Companion.AIRPODS_CONNECTION_DETECTED) {
|
if (intent?.action == AirPodsNotifications.AIRPODS_CONNECTION_DETECTED) {
|
||||||
device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
intent.getParcelableExtra("device", BluetoothDevice::class.java)!!
|
intent.getParcelableExtra("device", BluetoothDevice::class.java)!!
|
||||||
} else {
|
} else {
|
||||||
intent.getParcelableExtra("device") as BluetoothDevice?
|
intent.getParcelableExtra("device") as BluetoothDevice?
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.deviceName == "AirPods" && device?.name != null) {
|
if (config.deviceName == "AirPods" && device?.name != null) {
|
||||||
config.deviceName = device?.name ?: "AirPods"
|
config.deviceName = device?.name ?: "AirPods"
|
||||||
sharedPreferences.edit { putString("name", config.deviceName) }
|
sharedPreferences.edit { putString("name", config.deviceName) }
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d("AirPodsCrossDevice", CrossDevice.isAvailable.toString())
|
Log.d("AirPodsCrossDevice", CrossDevice.isAvailable.toString())
|
||||||
if (!CrossDevice.isAvailable) {
|
if (!CrossDevice.isAvailable) {
|
||||||
Log.d("AirPodsService", "${config.deviceName} connected")
|
Log.d("AirPodsService", "${config.deviceName} connected")
|
||||||
showPopup(this@AirPodsService, config.deviceName)
|
showPopup(this@AirPodsService, config.deviceName)
|
||||||
connectToSocket(device!!)
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
connectToSocket(device!!)
|
||||||
|
}
|
||||||
Log.d("AirPodsService", "Setting metadata")
|
Log.d("AirPodsService", "Setting metadata")
|
||||||
setMetadatas(device!!)
|
setMetadatas(device!!)
|
||||||
isConnectedLocally = true
|
isConnectedLocally = true
|
||||||
@@ -1191,7 +1262,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
batteryNotification.getBattery()
|
batteryNotification.getBattery()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (intent?.action == AirPodsNotifications.Companion.AIRPODS_DISCONNECTED) {
|
} else if (intent?.action == AirPodsNotifications.AIRPODS_DISCONNECTED) {
|
||||||
device = null
|
device = null
|
||||||
isConnectedLocally = false
|
isConnectedLocally = false
|
||||||
popupShown = false
|
popupShown = false
|
||||||
@@ -1203,7 +1274,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
if (intent?.action == "me.kavishdevar.librepods.cross_device_island") {
|
if (intent?.action == "me.kavishdevar.librepods.cross_device_island") {
|
||||||
showIsland(this@AirPodsService, batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level!!.coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level!!))
|
showIsland(this@AirPodsService, batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level!!.coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level!!))
|
||||||
} else if (intent?.action == AirPodsNotifications.Companion.DISCONNECT_RECEIVERS) {
|
} else if (intent?.action == AirPodsNotifications.DISCONNECT_RECEIVERS) {
|
||||||
try {
|
try {
|
||||||
context?.unregisterReceiver(this)
|
context?.unregisterReceiver(this)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -1225,8 +1296,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
}
|
}
|
||||||
|
|
||||||
val deviceIntentFilter = IntentFilter().apply {
|
val deviceIntentFilter = IntentFilter().apply {
|
||||||
addAction(AirPodsNotifications.Companion.AIRPODS_CONNECTION_DETECTED)
|
addAction(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED)
|
||||||
addAction(AirPodsNotifications.Companion.AIRPODS_DISCONNECTED)
|
addAction(AirPodsNotifications.AIRPODS_DISCONNECTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
@@ -1251,7 +1322,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
val connectedDevices = proxy.connectedDevices
|
val connectedDevices = proxy.connectedDevices
|
||||||
if (connectedDevices.isNotEmpty()) {
|
if (connectedDevices.isNotEmpty()) {
|
||||||
if (!CrossDevice.isAvailable) {
|
if (!CrossDevice.isAvailable) {
|
||||||
connectToSocket(device)
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
connectToSocket(device)
|
||||||
|
}
|
||||||
setMetadatas(device)
|
setMetadatas(device)
|
||||||
macAddress = device.address
|
macAddress = device.address
|
||||||
sharedPreferences.edit {
|
sharedPreferences.edit {
|
||||||
@@ -1259,7 +1332,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this@AirPodsService.sendBroadcast(
|
this@AirPodsService.sendBroadcast(
|
||||||
Intent(AirPodsNotifications.Companion.AIRPODS_CONNECTED)
|
Intent(AirPodsNotifications.AIRPODS_CONNECTED)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1295,7 +1368,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
fun takeOver() {
|
fun takeOver() {
|
||||||
Log.d("AirPodsService", "Taking over audio")
|
Log.d("AirPodsService", "Taking over audio")
|
||||||
CrossDevice.sendRemotePacket(CrossDevicePackets.REQUEST_DISCONNECT.packet)
|
CrossDevice.sendRemotePacket(CrossDevicePackets.REQUEST_DISCONNECT.packet)
|
||||||
Log.d("AirPodsService", macAddress)
|
Log.d("AirPodsService", macAddress)
|
||||||
@@ -1333,10 +1406,12 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
}
|
}
|
||||||
|
|
||||||
var lastException: Exception? = null
|
var lastException: Exception? = null
|
||||||
|
var attemptedConstructors = 0
|
||||||
|
|
||||||
for ((index, params) in constructorSpecs.withIndex()) {
|
for ((index, params) in constructorSpecs.withIndex()) {
|
||||||
try {
|
try {
|
||||||
Log.d("AirPodsService", "Trying constructor signature #${index + 1}")
|
Log.d("AirPodsService", "Trying constructor signature #${index + 1}")
|
||||||
|
attemptedConstructors++
|
||||||
return HiddenApiBypass.newInstance(BluetoothSocket::class.java, *params) as BluetoothSocket
|
return HiddenApiBypass.newInstance(BluetoothSocket::class.java, *params) as BluetoothSocket
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("AirPodsService", "Constructor signature #${index + 1} failed: ${e.message}")
|
Log.e("AirPodsService", "Constructor signature #${index + 1} failed: ${e.message}")
|
||||||
@@ -1344,9 +1419,10 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendToast("Failed to create BluetoothSocket!")
|
val errorMessage = "Failed to create BluetoothSocket after trying $attemptedConstructors constructor signatures"
|
||||||
|
Log.e("AirPodsService", errorMessage)
|
||||||
throw lastException ?: IllegalStateException("Failed to create BluetoothSocket")
|
showSocketConnectionFailureNotification(errorMessage)
|
||||||
|
throw lastException ?: IllegalStateException(errorMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.R)
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
@@ -1354,18 +1430,30 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
fun connectToSocket(device: BluetoothDevice) {
|
fun connectToSocket(device: BluetoothDevice) {
|
||||||
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
|
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
|
||||||
val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
|
val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
|
||||||
val isHooked = RadareOffsetFinder(this).isHookOffsetAvailable()
|
|
||||||
assert(isHooked) { "Hook offset not available, stopping" }
|
|
||||||
if (isConnectedLocally != true && !CrossDevice.isAvailable) {
|
if (isConnectedLocally != true && !CrossDevice.isAvailable) {
|
||||||
socket = try {
|
socket = try {
|
||||||
createBluetoothSocket(device, uuid)
|
createBluetoothSocket(device, uuid)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("AirPodsService", "Failed to create BluetoothSocket: ${e.message}")
|
Log.e("AirPodsService", "Failed to create BluetoothSocket: ${e.message}")
|
||||||
|
showSocketConnectionFailureNotification("Failed to create Bluetooth socket: ${e.message}")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
socket.connect()
|
runBlocking {
|
||||||
|
withTimeout(5000L) {
|
||||||
|
try {
|
||||||
|
socket.connect()
|
||||||
|
this@AirPodsService.device = device
|
||||||
|
} catch (e: Exception) {
|
||||||
|
showSocketConnectionFailureNotification("Socket created, but not connected. Is the Bluetooth process hooked?")
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!socket.isConnected) {
|
||||||
|
showSocketConnectionFailureNotification("Socket created, but not connected. Is the Bluetooth process hooked?")
|
||||||
|
}
|
||||||
|
}
|
||||||
this@AirPodsService.device = device
|
this@AirPodsService.device = device
|
||||||
isConnectedLocally = true
|
isConnectedLocally = true
|
||||||
socket.let { it ->
|
socket.let { it ->
|
||||||
@@ -1398,7 +1486,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
it.outputStream.flush()
|
it.outputStream.flush()
|
||||||
}, 5000)
|
}, 5000)
|
||||||
sendBroadcast(
|
sendBroadcast(
|
||||||
Intent(AirPodsNotifications.Companion.AIRPODS_CONNECTED)
|
Intent(AirPodsNotifications.AIRPODS_CONNECTED)
|
||||||
.putExtra("device", device)
|
.putExtra("device", device)
|
||||||
)
|
)
|
||||||
while (socket.isConnected == true) {
|
while (socket.isConnected == true) {
|
||||||
@@ -1408,7 +1496,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
var data: ByteArray = byteArrayOf()
|
var data: ByteArray = byteArrayOf()
|
||||||
if (bytesRead > 0) {
|
if (bytesRead > 0) {
|
||||||
data = buffer.copyOfRange(0, bytesRead)
|
data = buffer.copyOfRange(0, bytesRead)
|
||||||
sendBroadcast(Intent(AirPodsNotifications.Companion.AIRPODS_DATA).apply {
|
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DATA).apply {
|
||||||
putExtra("data", buffer.copyOfRange(0, bytesRead))
|
putExtra("data", buffer.copyOfRange(0, bytesRead))
|
||||||
})
|
})
|
||||||
val bytes = buffer.copyOfRange(0, bytesRead)
|
val bytes = buffer.copyOfRange(0, bytesRead)
|
||||||
@@ -1425,7 +1513,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
}
|
}
|
||||||
} else if (bytesRead == -1) {
|
} else if (bytesRead == -1) {
|
||||||
Log.d("AirPods Service", "Socket closed (bytesRead = -1)")
|
Log.d("AirPods Service", "Socket closed (bytesRead = -1)")
|
||||||
sendBroadcast(Intent(AirPodsNotifications.Companion.AIRPODS_DISCONNECTED))
|
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED))
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
var inEar = false
|
var inEar = false
|
||||||
@@ -1433,7 +1521,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
processData(data)
|
processData(data)
|
||||||
if (earDetectionNotification.isEarDetectionData(data)) {
|
if (earDetectionNotification.isEarDetectionData(data)) {
|
||||||
earDetectionNotification.setStatus(data)
|
earDetectionNotification.setStatus(data)
|
||||||
sendBroadcast(Intent(AirPodsNotifications.Companion.EAR_DETECTION_DATA).apply {
|
sendBroadcast(Intent(AirPodsNotifications.EAR_DETECTION_DATA).apply {
|
||||||
val list = earDetectionNotification.status
|
val list = earDetectionNotification.status
|
||||||
val bytes = ByteArray(2)
|
val bytes = ByteArray(2)
|
||||||
bytes[0] = list[0]
|
bytes[0] = list[0]
|
||||||
@@ -1478,17 +1566,17 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
val state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED)
|
val state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED)
|
||||||
val previousState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED)
|
val previousState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED)
|
||||||
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
|
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
|
||||||
|
|
||||||
Log.d("MediaController", "A2DP state changed: $previousState -> $state for device: ${device?.address}")
|
Log.d("MediaController", "A2DP state changed: $previousState -> $state for device: ${device?.address}")
|
||||||
|
|
||||||
if (state == BluetoothProfile.STATE_CONNECTED &&
|
if (state == BluetoothProfile.STATE_CONNECTED &&
|
||||||
previousState != BluetoothProfile.STATE_CONNECTED &&
|
previousState != BluetoothProfile.STATE_CONNECTED &&
|
||||||
device?.address == this@AirPodsService.device?.address) {
|
device?.address == this@AirPodsService.device?.address) {
|
||||||
|
|
||||||
Log.d("MediaController", "A2DP connected, sending play command")
|
Log.d("MediaController", "A2DP connected, sending play command")
|
||||||
MediaController.sendPlay()
|
MediaController.sendPlay()
|
||||||
MediaController.iPausedTheMedia = false
|
MediaController.iPausedTheMedia = false
|
||||||
|
|
||||||
context.unregisterReceiver(this)
|
context.unregisterReceiver(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1559,7 +1647,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
}
|
}
|
||||||
|
|
||||||
val earIntentFilter =
|
val earIntentFilter =
|
||||||
IntentFilter(AirPodsNotifications.Companion.EAR_DETECTION_DATA)
|
IntentFilter(AirPodsNotifications.EAR_DETECTION_DATA)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
this@AirPodsService.registerReceiver(
|
this@AirPodsService.registerReceiver(
|
||||||
earReceiver, earIntentFilter,
|
earReceiver, earIntentFilter,
|
||||||
@@ -1582,7 +1670,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
CrossDevice.sendRemotePacket(data)
|
CrossDevice.sendRemotePacket(data)
|
||||||
CrossDevice.batteryBytes = data
|
CrossDevice.batteryBytes = data
|
||||||
batteryNotification.setBattery(data)
|
batteryNotification.setBattery(data)
|
||||||
sendBroadcast(Intent(AirPodsNotifications.Companion.BATTERY_DATA).apply {
|
sendBroadcast(Intent(AirPodsNotifications.BATTERY_DATA).apply {
|
||||||
putParcelableArrayListExtra(
|
putParcelableArrayListExtra(
|
||||||
"data",
|
"data",
|
||||||
ArrayList(batteryNotification.getBattery())
|
ArrayList(batteryNotification.getBattery())
|
||||||
@@ -1613,7 +1701,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
conversationAwarenessNotification.setData(data)
|
conversationAwarenessNotification.setData(data)
|
||||||
sendBroadcast(Intent(AirPodsNotifications.Companion.CA_DATA).apply {
|
sendBroadcast(Intent(AirPodsNotifications.CA_DATA).apply {
|
||||||
putExtra("data", conversationAwarenessNotification.status)
|
putExtra("data", conversationAwarenessNotification.status)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1637,12 +1725,20 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
Log.d("AirPods Service", "Socket closed")
|
Log.d("AirPods Service", "Socket closed")
|
||||||
isConnectedLocally = false
|
isConnectedLocally = false
|
||||||
socket.close()
|
socket.close()
|
||||||
sendBroadcast(Intent(AirPodsNotifications.Companion.AIRPODS_DISCONNECTED))
|
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
Log.d("AirPodsService", "Failed to connect to socket")
|
Log.d("AirPodsService", "Failed to connect to socket: ${e.message}")
|
||||||
|
showSocketConnectionFailureNotification("Failed to establish connection: ${e.message}")
|
||||||
|
isConnectedLocally = false
|
||||||
|
this@AirPodsService.device = device
|
||||||
|
updateNotificationContent(
|
||||||
|
true,
|
||||||
|
config.deviceName,
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1686,7 +1782,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
socket.outputStream?.write(byteArray)
|
socket.outputStream?.write(byteArray)
|
||||||
socket.outputStream?.flush()
|
socket.outputStream?.flush()
|
||||||
} else {
|
} else {
|
||||||
Log.d("AirPodsService", "Cannot send packet: Socket not initialized or connected")
|
Log.d("AirPodsService", "Can't send packet: Socket not initialized or connected")
|
||||||
|
showSocketConnectionFailureNotification("Socket created, but not connected. Is the Bluetooth process hooked?")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("AirPodsService", "Error sending packet: ${e.message}")
|
Log.e("AirPodsService", "Error sending packet: ${e.message}")
|
||||||
@@ -1705,7 +1802,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
socket.outputStream?.write(packet)
|
socket.outputStream?.write(packet)
|
||||||
socket.outputStream?.flush()
|
socket.outputStream?.flush()
|
||||||
} else {
|
} else {
|
||||||
Log.d("AirPodsService", "Cannot send packet: Socket not initialized or connected")
|
Log.d("AirPodsService", "Can't send packet: Socket not initialized or connected")
|
||||||
|
showSocketConnectionFailureNotification("Socket created, but not connected. Is the Bluetooth process hooked?")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("AirPodsService", "Error sending packet: ${e.message}")
|
Log.e("AirPodsService", "Error sending packet: ${e.message}")
|
||||||
@@ -1982,12 +2080,12 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
) + nameBytes
|
) + nameBytes
|
||||||
sendPacket(bytes)
|
sendPacket(bytes)
|
||||||
val hex = bytes.joinToString(" ") { "%02X".format(it) }
|
val hex = bytes.joinToString(" ") { "%02X".format(it) }
|
||||||
|
|
||||||
if (config.deviceName != name) {
|
if (config.deviceName != name) {
|
||||||
config.deviceName = name
|
config.deviceName = name
|
||||||
sharedPreferences.edit { putString("name", name) }
|
sharedPreferences.edit { putString("name", name) }
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNotificationContent(true, name, batteryNotification.getBattery())
|
updateNotificationContent(true, name, batteryNotification.getBattery())
|
||||||
Log.d("AirPodsService", "setName: $name, sent packet: $hex")
|
Log.d("AirPodsService", "setName: $name, sent packet: $hex")
|
||||||
}
|
}
|
||||||
@@ -2000,7 +2098,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
"04 00 04 00 17 00 00 00 10 00 12 00 08 E${if (enabled) "6" else "5"} 05 10 02 42 0B 08 50 10 02 1A 05 02 ${if (enabled) "32" else "00"} 00 00 00"
|
"04 00 04 00 17 00 00 00 10 00 12 00 08 E${if (enabled) "6" else "5"} 05 10 02 42 0B 08 50 10 02 1A 05 02 ${if (enabled) "32" else "00"} 00 00 00"
|
||||||
bytes = hex.split(" ").map { it.toInt(16).toByte() }.toByteArray()
|
bytes = hex.split(" ").map { it.toInt(16).toByte() }.toByteArray()
|
||||||
sendPacket(bytes)
|
sendPacket(bytes)
|
||||||
|
|
||||||
if (config.personalizedVolume != enabled) {
|
if (config.personalizedVolume != enabled) {
|
||||||
config.personalizedVolume = enabled
|
config.personalizedVolume = enabled
|
||||||
sharedPreferences.edit { putBoolean("personalized_volume", enabled) }
|
sharedPreferences.edit { putBoolean("personalized_volume", enabled) }
|
||||||
@@ -2011,7 +2109,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
val hex = "52 1B 00 0${if (enabled) "1" else "0"}"
|
val hex = "52 1B 00 0${if (enabled) "1" else "0"}"
|
||||||
val bytes = hex.split(" ").map { it.toInt(16).toByte() }.toByteArray()
|
val bytes = hex.split(" ").map { it.toInt(16).toByte() }.toByteArray()
|
||||||
sendPacket(bytes)
|
sendPacket(bytes)
|
||||||
|
|
||||||
if (config.loudSoundReduction != enabled) {
|
if (config.loudSoundReduction != enabled) {
|
||||||
config.loudSoundReduction = enabled
|
config.loudSoundReduction = enabled
|
||||||
sharedPreferences.edit { putBoolean("loud_sound_reduction", enabled) }
|
sharedPreferences.edit { putBoolean("loud_sound_reduction", enabled) }
|
||||||
@@ -2169,9 +2267,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
clearPacketLogs()
|
clearPacketLogs()
|
||||||
Log.d("AirPodsService", "Service stopped is being destroyed for some reason!")
|
Log.d("AirPodsService", "Service stopped is being destroyed for some reason!")
|
||||||
|
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
unregisterReceiver(bluetoothReceiver)
|
unregisterReceiver(bluetoothReceiver)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
@@ -21,14 +21,13 @@ package me.kavishdevar.librepods.utils
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.media.AudioPlaybackConfiguration
|
import android.media.AudioPlaybackConfiguration
|
||||||
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import me.kavishdevar.librepods.services.ServiceManager
|
import me.kavishdevar.librepods.services.ServiceManager
|
||||||
import kotlin.div
|
|
||||||
import kotlin.text.compareTo
|
|
||||||
import kotlin.times
|
|
||||||
|
|
||||||
object MediaController {
|
object MediaController {
|
||||||
private var initialVolume: Int? = null
|
private var initialVolume: Int? = null
|
||||||
@@ -76,6 +75,7 @@ object MediaController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val cb = object : AudioManager.AudioPlaybackCallback() {
|
val cb = object : AudioManager.AudioPlaybackCallback() {
|
||||||
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
override fun onPlaybackConfigChanged(configs: MutableList<AudioPlaybackConfiguration>?) {
|
override fun onPlaybackConfigChanged(configs: MutableList<AudioPlaybackConfiguration>?) {
|
||||||
super.onPlaybackConfigChanged(configs)
|
super.onPlaybackConfigChanged(configs)
|
||||||
Log.d("MediaController", "Playback config changed, iPausedTheMedia: $iPausedTheMedia")
|
Log.d("MediaController", "Playback config changed, iPausedTheMedia: $iPausedTheMedia")
|
||||||
@@ -140,7 +140,7 @@ object MediaController {
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
fun startSpeaking() {
|
fun startSpeaking() {
|
||||||
Log.d("MediaController", "Starting speaking max vol: ${audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)}, current vol: ${audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)}, conversationalAwarenessVolume: $conversationalAwarenessVolume, relativeVolume: $relativeVolume")
|
Log.d("MediaController", "Starting speaking max vol: ${audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)}, current vol: ${audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)}, conversationalAwarenessVolume: $conversationalAwarenessVolume, relativeVolume: $relativeVolume")
|
||||||
|
|
||||||
if (initialVolume == null) {
|
if (initialVolume == null) {
|
||||||
initialVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
|
initialVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
|
||||||
Log.d("MediaController", "Initial Volume: $initialVolume")
|
Log.d("MediaController", "Initial Volume: $initialVolume")
|
||||||
|
|||||||
Reference in New Issue
Block a user