diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index e7f26a1..c960427 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -35,8 +35,6 @@
-
-
-
-->
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt
index 46b6415..27e4679 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt
@@ -1,24 +1,23 @@
/*
* LibrePods - AirPods liberated from Appleās ecosystem
- *
+ *
* Copyright (C) 2025 LibrePods contributors
- *
+ *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package me.kavishdevar.librepods
-import android.Manifest
import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
@@ -29,26 +28,12 @@ import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
-import androidx.annotation.RequiresPermission
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
-import me.kavishdevar.librepods.screens.AccessibilitySettingsScreen
-import me.kavishdevar.librepods.screens.EqualizerSettingsScreen
-import me.kavishdevar.librepods.ui.theme.LibrePodsTheme
-import org.lsposed.hiddenapibypass.HiddenApiBypass
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -56,36 +41,40 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
+import me.kavishdevar.librepods.screens.AccessibilitySettingsScreen
+import me.kavishdevar.librepods.screens.EqualizerSettingsScreen
+import me.kavishdevar.librepods.ui.theme.LibrePodsTheme
+import org.lsposed.hiddenapibypass.HiddenApiBypass
import java.io.IOException
import java.nio.ByteBuffer
import java.nio.ByteOrder
+@Suppress("PrivatePropertyName")
class CustomDevice : ComponentActivity() {
private val TAG = "AirPodsAccessibilitySettings"
private var socket: BluetoothSocket? = null
private val deviceAddress = "28:2D:7F:C2:05:5B"
- private val psm = 31
private val uuid: ParcelUuid = ParcelUuid.fromString("00000000-0000-0000-0000-00000000000")
// Data states
private val isConnected = mutableStateOf(false)
- private val leftAmplification = mutableStateOf(1.0f)
- private val leftTone = mutableStateOf(1.0f)
- private val leftAmbientNoiseReduction = mutableStateOf(0.5f)
+ private val leftAmplification = mutableFloatStateOf(1.0f)
+ private val leftTone = mutableFloatStateOf(1.0f)
+ private val leftAmbientNoiseReduction = mutableFloatStateOf(0.5f)
private val leftConversationBoost = mutableStateOf(false)
private val leftEQ = mutableStateOf(FloatArray(8) { 50.0f })
- private val rightAmplification = mutableStateOf(1.0f)
- private val rightTone = mutableStateOf(1.0f)
- private val rightAmbientNoiseReduction = mutableStateOf(0.5f)
+ private val rightAmplification = mutableFloatStateOf(1.0f)
+ private val rightTone = mutableFloatStateOf(1.0f)
+ private val rightAmbientNoiseReduction = mutableFloatStateOf(0.5f)
private val rightConversationBoost = mutableStateOf(false)
private val rightEQ = mutableStateOf(FloatArray(8) { 50.0f })
private val singleMode = mutableStateOf(false)
- private val amplification = mutableStateOf(1.0f)
- private val balance = mutableStateOf(0.5f)
+ private val amplification = mutableFloatStateOf(1.0f)
+ private val balance = mutableFloatStateOf(0.5f)
- private val retryCount = mutableStateOf(0)
+ private val retryCount = mutableIntStateOf(0)
private val showRetryButton = mutableStateOf(false)
private val maxRetries = 3
@@ -146,18 +135,19 @@ class CustomDevice : ComponentActivity() {
socket?.close()
}
+ @SuppressLint("MissingPermission")
private suspend fun connectL2CAP() {
- retryCount.value = 0
+ retryCount.intValue = 0
// Close any existing socket
socket?.close()
socket = null
- while (retryCount.value < maxRetries) {
+ while (retryCount.intValue < maxRetries) {
try {
- Log.d(TAG, "Starting L2CAP connection setup, attempt ${retryCount.value + 1}")
+ Log.d(TAG, "Starting L2CAP connection setup, attempt ${retryCount.intValue + 1}")
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
val manager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager
val device: BluetoothDevice = manager.adapter.getRemoteDevice(deviceAddress)
- socket = createBluetoothSocket(device, psm)
+ socket = createBluetoothSocket(device)
withTimeout(5000L) {
socket?.connect()
@@ -177,9 +167,9 @@ class CustomDevice : ComponentActivity() {
return
} catch (e: Exception) {
- Log.e(TAG, "Failed to connect, attempt ${retryCount.value + 1}: ${e.message}")
- retryCount.value++
- if (retryCount.value < maxRetries) {
+ Log.e(TAG, "Failed to connect, attempt ${retryCount.intValue + 1}: ${e.message}")
+ retryCount.intValue++
+ if (retryCount.intValue < maxRetries) {
delay(2000) // Wait 2 seconds before retry
}
}
@@ -193,7 +183,7 @@ class CustomDevice : ComponentActivity() {
}
}
- private fun createBluetoothSocket(device: BluetoothDevice, psm: Int): BluetoothSocket {
+ private fun createBluetoothSocket(device: BluetoothDevice): BluetoothSocket {
val type = 3 // L2CAP
val constructorSpecs = listOf(
arrayOf(device, type, true, true, 31, uuid),
@@ -300,18 +290,18 @@ class CustomDevice : ComponentActivity() {
leftEQ.value = newLeftEQ
if (singleMode.value) rightEQ.value = newLeftEQ
- leftAmplification.value = buffer.float
- Log.d(TAG, "Parsed left amplification: ${leftAmplification.value}")
- leftTone.value = buffer.float
- Log.d(TAG, "Parsed left tone: ${leftTone.value}")
- if (singleMode.value) rightTone.value = leftTone.value
+ leftAmplification.floatValue = buffer.float
+ Log.d(TAG, "Parsed left amplification: ${leftAmplification.floatValue}")
+ leftTone.floatValue = buffer.float
+ Log.d(TAG, "Parsed left tone: ${leftTone.floatValue}")
+ if (singleMode.value) rightTone.floatValue = leftTone.floatValue
val leftConvFloat = buffer.float
leftConversationBoost.value = leftConvFloat > 0.5f
Log.d(TAG, "Parsed left conversation boost: $leftConvFloat (${leftConversationBoost.value})")
if (singleMode.value) rightConversationBoost.value = leftConversationBoost.value
- leftAmbientNoiseReduction.value = buffer.float
- Log.d(TAG, "Parsed left ambient noise reduction: ${leftAmbientNoiseReduction.value}")
- if (singleMode.value) rightAmbientNoiseReduction.value = leftAmbientNoiseReduction.value
+ leftAmbientNoiseReduction.floatValue = buffer.float
+ Log.d(TAG, "Parsed left ambient noise reduction: ${leftAmbientNoiseReduction.floatValue}")
+ if (singleMode.value) rightAmbientNoiseReduction.floatValue = leftAmbientNoiseReduction.floatValue
// Right bud
val newRightEQ = rightEQ.value.copyOf()
@@ -321,24 +311,24 @@ class CustomDevice : ComponentActivity() {
}
rightEQ.value = newRightEQ
- rightAmplification.value = buffer.float
- Log.d(TAG, "Parsed right amplification: ${rightAmplification.value}")
- rightTone.value = buffer.float
- Log.d(TAG, "Parsed right tone: ${rightTone.value}")
+ rightAmplification.floatValue = buffer.float
+ Log.d(TAG, "Parsed right amplification: ${rightAmplification.floatValue}")
+ rightTone.floatValue = buffer.float
+ Log.d(TAG, "Parsed right tone: ${rightTone.floatValue}")
val rightConvFloat = buffer.float
rightConversationBoost.value = rightConvFloat > 0.5f
Log.d(TAG, "Parsed right conversation boost: $rightConvFloat (${rightConversationBoost.value})")
- rightAmbientNoiseReduction.value = buffer.float
- Log.d(TAG, "Parsed right ambient noise reduction: ${rightAmbientNoiseReduction.value}")
+ rightAmbientNoiseReduction.floatValue = buffer.float
+ Log.d(TAG, "Parsed right ambient noise reduction: ${rightAmbientNoiseReduction.floatValue}")
Log.d(TAG, "Settings parsed successfully")
// Update single mode values if in single mode
if (singleMode.value) {
- val avg = (leftAmplification.value + rightAmplification.value) / 2
- amplification.value = avg.coerceIn(0f, 1f)
- val diff = rightAmplification.value - leftAmplification.value
- balance.value = (0.5f + diff / (2 * avg)).coerceIn(0f, 1f)
+ val avg = (leftAmplification.floatValue + rightAmplification.floatValue) / 2
+ amplification.floatValue = avg.coerceIn(0f, 1f)
+ val diff = rightAmplification.floatValue - leftAmplification.floatValue
+ balance.floatValue = (0.5f + diff / (2 * avg)).coerceIn(0f, 1f)
}
}
@@ -363,19 +353,19 @@ class CustomDevice : ComponentActivity() {
for (eq in leftEQ.value) {
buffer.putFloat(eq)
}
- buffer.putFloat(leftAmplification.value)
- buffer.putFloat(leftTone.value)
+ buffer.putFloat(leftAmplification.floatValue)
+ buffer.putFloat(leftTone.floatValue)
buffer.putFloat(if (leftConversationBoost.value) 1.0f else 0.0f)
- buffer.putFloat(leftAmbientNoiseReduction.value)
+ buffer.putFloat(leftAmbientNoiseReduction.floatValue)
// Right bud
for (eq in rightEQ.value) {
buffer.putFloat(eq)
}
- buffer.putFloat(rightAmplification.value)
- buffer.putFloat(rightTone.value)
+ buffer.putFloat(rightAmplification.floatValue)
+ buffer.putFloat(rightTone.floatValue)
buffer.putFloat(if (rightConversationBoost.value) 1.0f else 0.0f)
- buffer.putFloat(rightAmbientNoiseReduction.value)
+ buffer.putFloat(rightAmbientNoiseReduction.floatValue)
val packet = buffer.array()
Log.d(TAG, "Packet length: ${packet.size}")
@@ -393,4 +383,4 @@ class CustomDevice : ComponentActivity() {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt
index 1d35813..9dba146 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt
@@ -97,6 +97,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.core.content.edit
+import androidx.core.net.toUri
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
@@ -104,6 +106,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.MultiplePermissionsState
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberMultiplePermissionsState
+import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import me.kavishdevar.librepods.constants.AirPodsNotifications
import me.kavishdevar.librepods.screens.AirPodsSettingsScreen
import me.kavishdevar.librepods.screens.AppSettingsScreen
@@ -123,6 +126,7 @@ import kotlin.io.encoding.ExperimentalEncodingApi
lateinit var serviceConnection: ServiceConnection
lateinit var connectionStatusReceiver: BroadcastReceiver
+@ExperimentalHazeMaterialsApi
@ExperimentalMaterial3Api
class MainActivity : ComponentActivity() {
companion object {
@@ -137,8 +141,10 @@ class MainActivity : ComponentActivity() {
setContent {
LibrePodsTheme {
- getSharedPreferences("settings", MODE_PRIVATE).edit().putLong("textColor",
- MaterialTheme.colorScheme.onSurface.toArgb().toLong()).apply()
+ getSharedPreferences("settings", MODE_PRIVATE).edit {
+ putLong(
+ "textColor",
+ MaterialTheme.colorScheme.onSurface.toArgb().toLong())}
Main()
}
}
@@ -207,8 +213,7 @@ class MainActivity : ComponentActivity() {
}
private fun handleAddMagicKeys(uri: Uri) {
- val context = this
- val sharedPreferences = getSharedPreferences("settings", Context.MODE_PRIVATE)
+ val sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE)
val irkHex = uri.getQueryParameter("irk")
val encKeyHex = uri.getQueryParameter("enc_key")
@@ -217,13 +222,13 @@ class MainActivity : ComponentActivity() {
if (irkHex != null && validateHexInput(irkHex)) {
val irkBytes = hexStringToByteArray(irkHex)
val irkBase64 = Base64.encode(irkBytes)
- sharedPreferences.edit().putString("IRK", irkBase64).apply()
+ sharedPreferences.edit {putString("IRK", irkBase64)}
}
if (encKeyHex != null && validateHexInput(encKeyHex)) {
val encKeyBytes = hexStringToByteArray(encKeyHex)
val encKeyBase64 = Base64.encode(encKeyBytes)
- sharedPreferences.edit().putString("ENC_KEY", encKeyBase64).apply()
+ sharedPreferences.edit { putString("ENC_KEY", encKeyBase64)}
}
Toast.makeText(this, "Magic keys added successfully!", Toast.LENGTH_SHORT).show()
@@ -247,6 +252,7 @@ class MainActivity : ComponentActivity() {
}
}
+@ExperimentalHazeMaterialsApi
@SuppressLint("MissingPermission", "InlinedApi", "UnspecifiedRegisterReceiverFlag")
@OptIn(ExperimentalPermissionsApi::class)
@Composable
@@ -404,6 +410,7 @@ fun Main() {
}
}
+@ExperimentalHazeMaterialsApi
@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class)
@Composable
fun PermissionsScreen(
@@ -586,7 +593,7 @@ fun PermissionsScreen(
onClick = {
val intent = Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
- Uri.parse("package:${context.packageName}")
+ "package:${context.packageName}".toUri()
)
context.startActivity(intent)
onOverlaySettingsReturn()
@@ -616,9 +623,9 @@ fun PermissionsScreen(
Button(
onClick = {
- val editor = context.getSharedPreferences("settings", MODE_PRIVATE).edit()
- editor.putBoolean("overlay_permission_skipped", true)
- editor.apply()
+ context.getSharedPreferences("settings", MODE_PRIVATE).edit {
+ putBoolean("overlay_permission_skipped", true)
+ }
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/QuickSettingsDialogActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/QuickSettingsDialogActivity.kt
index d8a79d2..b30c3ed 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/QuickSettingsDialogActivity.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/QuickSettingsDialogActivity.kt
@@ -133,7 +133,7 @@ class QuickSettingsDialogActivity : ComponentActivity() {
window.setGravity(Gravity.BOTTOM)
Intent(this, AirPodsService::class.java).also { intent ->
- bindService(intent, connection, Context.BIND_AUTO_CREATE)
+ bindService(intent, connection, BIND_AUTO_CREATE)
}
setContent {
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt
index 75cbd2a..42c942b 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt
@@ -134,7 +134,7 @@ fun AccessibilitySettings() {
textColor = textColor
)
- val volumeSwipeSpeedOptions = mapOf(
+ val volumeSwipeSpeedOptions = mapOf(
1.toByte() to "Default",
2.toByte() to "Longer",
3.toByte() to "Longest"
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt
index d111c0a..abd8d14 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt
@@ -23,10 +23,8 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -135,4 +133,4 @@ fun AccessibilitySliderPreview() {
onValueChange = {},
valueRange = 0f..2f
)
-}
\ No newline at end of file
+}
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt
index c4740d7..4f90662 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt
@@ -99,7 +99,7 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) {
batteryStatus.value = service.getBattery()
if (preview) {
- batteryStatus.value = listOf(
+ batteryStatus.value = listOf(
Battery(BatteryComponent.LEFT, 100, BatteryStatus.CHARGING),
Battery(BatteryComponent.RIGHT, 50, BatteryStatus.NOT_CHARGING),
Battery(BatteryComponent.CASE, 5, BatteryStatus.CHARGING)
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ControlCenterButton.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ControlCenterButton.kt
index 2262682..6de2876 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ControlCenterButton.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ControlCenterButton.kt
@@ -15,7 +15,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-
+
+@file:Suppress("unused")
+
package me.kavishdevar.librepods.composables
import androidx.compose.animation.animateColorAsState
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt
index f3e320b..2cb6e46 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt
@@ -50,6 +50,7 @@ import androidx.compose.ui.unit.sp
import me.kavishdevar.librepods.services.AirPodsService
import me.kavishdevar.librepods.utils.AACPManager
import kotlin.io.encoding.ExperimentalEncodingApi
+import androidx.core.content.edit
@Composable
fun IndependentToggle(name: String, service: AirPodsService? = null, functionName: String? = null, sharedPreferences: SharedPreferences, default: Boolean = false, controlCommandIdentifier: AACPManager.Companion.ControlCommandIdentifiers? = null) {
@@ -70,7 +71,7 @@ fun IndependentToggle(name: String, service: AirPodsService? = null, functionNam
fun cb() {
if (controlCommandIdentifier == null) {
- sharedPreferences.edit().putBoolean(snakeCasedName, checked).apply()
+ sharedPreferences.edit { putBoolean(snakeCasedName, checked) }
}
if (functionName != null && service != null) {
val method =
@@ -127,4 +128,4 @@ fun IndependentToggle(name: String, service: AirPodsService? = null, functionNam
@Composable
fun IndependentTogglePreview() {
IndependentToggle("Test", AirPodsService(), "test", LocalContext.current.getSharedPreferences("preview", 0), true)
-}
\ No newline at end of file
+}
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/constants/Packets.kt b/android/app/src/main/java/me/kavishdevar/librepods/constants/Packets.kt
index 6c8d661..91f79f4 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/constants/Packets.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/constants/Packets.kt
@@ -244,7 +244,7 @@ fun isHeadTrackingData(data: ByteArray): Boolean {
)
for (i in prefixPattern.indices) {
- if (data[i] != prefixPattern[i].toByte()) return false
+ if (data[i] != prefixPattern[i]) return false
}
if (data[10] != 0x44.toByte() && data[10] != 0x45.toByte()) return false
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/constants/StemAction.kt b/android/app/src/main/java/me/kavishdevar/librepods/constants/StemAction.kt
index 3c5be49..fabe01a 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/constants/StemAction.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/constants/StemAction.kt
@@ -18,7 +18,6 @@
package me.kavishdevar.librepods.constants
-import me.kavishdevar.librepods.constants.StemAction.entries
import me.kavishdevar.librepods.utils.AACPManager
enum class StemAction {
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt
index 824098c..f6d1fc2 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt
@@ -18,7 +18,6 @@
package me.kavishdevar.librepods.screens
-import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -469,4 +468,4 @@ fun AccessibilitySettingsScreen(
Spacer(modifier = Modifier.height(16.dp))
}
}
-}
\ No newline at end of file
+}
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt
index 308c280..d49021e 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt
@@ -57,8 +57,6 @@ import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
-import androidx.compose.material3.TextField
-import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -86,6 +84,7 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.core.content.edit
import androidx.navigation.NavController
import dev.chrisbanes.haze.HazeEffectScope
import dev.chrisbanes.haze.HazeState
@@ -186,11 +185,11 @@ fun AppSettingsScreen(navController: NavController) {
var bleOnlyMode by remember {
mutableStateOf(sharedPreferences.getBoolean("ble_only_mode", false))
}
-
+
// Ensure the default value is properly set if not exists
LaunchedEffect(Unit) {
if (!sharedPreferences.contains("ble_only_mode")) {
- sharedPreferences.edit().putBoolean("ble_only_mode", false).apply()
+ sharedPreferences.edit { putBoolean("ble_only_mode", false) }
}
}
@@ -312,7 +311,7 @@ fun AppSettingsScreen(navController: NavController) {
interactionSource = remember { MutableInteractionSource() }
) {
showPhoneBatteryInWidget = !showPhoneBatteryInWidget
- sharedPreferences.edit().putBoolean("show_phone_battery_in_widget", showPhoneBatteryInWidget).apply()
+ sharedPreferences.edit { putBoolean("show_phone_battery_in_widget", showPhoneBatteryInWidget)}
},
verticalAlignment = Alignment.CenterVertically
) {
@@ -340,7 +339,7 @@ fun AppSettingsScreen(navController: NavController) {
checked = showPhoneBatteryInWidget,
onCheckedChange = {
showPhoneBatteryInWidget = it
- sharedPreferences.edit().putBoolean("show_phone_battery_in_widget", it).apply()
+ sharedPreferences.edit { putBoolean("show_phone_battery_in_widget", it)}
}
)
}
@@ -376,7 +375,7 @@ fun AppSettingsScreen(navController: NavController) {
interactionSource = remember { MutableInteractionSource() }
) {
bleOnlyMode = !bleOnlyMode
- sharedPreferences.edit().putBoolean("ble_only_mode", bleOnlyMode).apply()
+ sharedPreferences.edit { putBoolean("ble_only_mode", bleOnlyMode)}
},
verticalAlignment = Alignment.CenterVertically
) {
@@ -403,7 +402,7 @@ fun AppSettingsScreen(navController: NavController) {
checked = bleOnlyMode,
onCheckedChange = {
bleOnlyMode = it
- sharedPreferences.edit().putBoolean("ble_only_mode", it).apply()
+ sharedPreferences.edit { putBoolean("ble_only_mode", it)}
}
)
}
@@ -440,12 +439,12 @@ fun AppSettingsScreen(navController: NavController) {
fun updateConversationalAwarenessPauseMusic(enabled: Boolean) {
conversationalAwarenessPauseMusicEnabled = enabled
- sharedPreferences.edit().putBoolean("conversational_awareness_pause_music", enabled).apply()
+ sharedPreferences.edit { putBoolean("conversational_awareness_pause_music", enabled)}
}
fun updateRelativeConversationalAwarenessVolume(enabled: Boolean) {
relativeConversationalAwarenessVolumeEnabled = enabled
- sharedPreferences.edit().putBoolean("relative_conversational_awareness_volume", enabled).apply()
+ sharedPreferences.edit { putBoolean("relative_conversational_awareness_volume", enabled)}
}
Row(
@@ -541,7 +540,7 @@ fun AppSettingsScreen(navController: NavController) {
value = sliderValue.floatValue,
onValueChange = {
sliderValue.floatValue = it
- sharedPreferences.edit().putInt("conversational_awareness_volume", it.toInt()).apply()
+ sharedPreferences.edit { putInt("conversational_awareness_volume", it.toInt())}
},
valueRange = 10f..85f,
onValueChangeFinished = {
@@ -639,7 +638,7 @@ fun AppSettingsScreen(navController: NavController) {
) {
fun updateQsClickBehavior(enabled: Boolean) {
openDialogForControlling = enabled
- sharedPreferences.edit().putString("qs_click_behavior", if (enabled) "dialog" else "cycle").apply()
+ sharedPreferences.edit { putString("qs_click_behavior", if (enabled) "dialog" else "cycle")}
}
Row(
@@ -708,7 +707,7 @@ fun AppSettingsScreen(navController: NavController) {
) {
fun updateDisconnectWhenNotWearing(enabled: Boolean) {
disconnectWhenNotWearing = enabled
- sharedPreferences.edit().putBoolean("disconnect_when_not_wearing", enabled).apply()
+ sharedPreferences.edit { putBoolean("disconnect_when_not_wearing", enabled)}
}
Row(
@@ -789,7 +788,7 @@ fun AppSettingsScreen(navController: NavController) {
interactionSource = remember { MutableInteractionSource() }
) {
takeoverWhenDisconnected = !takeoverWhenDisconnected
- sharedPreferences.edit().putBoolean("takeover_when_disconnected", takeoverWhenDisconnected).apply()
+ sharedPreferences.edit { putBoolean("takeover_when_disconnected", takeoverWhenDisconnected)}
},
verticalAlignment = Alignment.CenterVertically
) {
@@ -817,7 +816,7 @@ fun AppSettingsScreen(navController: NavController) {
checked = takeoverWhenDisconnected,
onCheckedChange = {
takeoverWhenDisconnected = it
- sharedPreferences.edit().putBoolean("takeover_when_disconnected", it).apply()
+ sharedPreferences.edit { putBoolean("takeover_when_disconnected", it)}
}
)
}
@@ -830,7 +829,7 @@ fun AppSettingsScreen(navController: NavController) {
interactionSource = remember { MutableInteractionSource() }
) {
takeoverWhenIdle = !takeoverWhenIdle
- sharedPreferences.edit().putBoolean("takeover_when_idle", takeoverWhenIdle).apply()
+ sharedPreferences.edit { putBoolean("takeover_when_idle", takeoverWhenIdle)}
},
verticalAlignment = Alignment.CenterVertically
) {
@@ -858,7 +857,7 @@ fun AppSettingsScreen(navController: NavController) {
checked = takeoverWhenIdle,
onCheckedChange = {
takeoverWhenIdle = it
- sharedPreferences.edit().putBoolean("takeover_when_idle", it).apply()
+ sharedPreferences.edit { putBoolean("takeover_when_idle", it)}
}
)
}
@@ -871,7 +870,7 @@ fun AppSettingsScreen(navController: NavController) {
interactionSource = remember { MutableInteractionSource() }
) {
takeoverWhenMusic = !takeoverWhenMusic
- sharedPreferences.edit().putBoolean("takeover_when_music", takeoverWhenMusic).apply()
+ sharedPreferences.edit { putBoolean("takeover_when_music", takeoverWhenMusic)}
},
verticalAlignment = Alignment.CenterVertically
) {
@@ -899,7 +898,7 @@ fun AppSettingsScreen(navController: NavController) {
checked = takeoverWhenMusic,
onCheckedChange = {
takeoverWhenMusic = it
- sharedPreferences.edit().putBoolean("takeover_when_music", it).apply()
+ sharedPreferences.edit { putBoolean("takeover_when_music", it)}
}
)
}
@@ -912,7 +911,7 @@ fun AppSettingsScreen(navController: NavController) {
interactionSource = remember { MutableInteractionSource() }
) {
takeoverWhenCall = !takeoverWhenCall
- sharedPreferences.edit().putBoolean("takeover_when_call", takeoverWhenCall).apply()
+ sharedPreferences.edit { putBoolean("takeover_when_call", takeoverWhenCall)}
},
verticalAlignment = Alignment.CenterVertically
) {
@@ -940,7 +939,7 @@ fun AppSettingsScreen(navController: NavController) {
checked = takeoverWhenCall,
onCheckedChange = {
takeoverWhenCall = it
- sharedPreferences.edit().putBoolean("takeover_when_call", it).apply()
+ sharedPreferences.edit { putBoolean("takeover_when_call", it)}
}
)
}
@@ -963,7 +962,7 @@ fun AppSettingsScreen(navController: NavController) {
interactionSource = remember { MutableInteractionSource() }
) {
takeoverWhenRingingCall = !takeoverWhenRingingCall
- sharedPreferences.edit().putBoolean("takeover_when_ringing_call", takeoverWhenRingingCall).apply()
+ sharedPreferences.edit { putBoolean("takeover_when_ringing_call", takeoverWhenRingingCall)}
},
verticalAlignment = Alignment.CenterVertically
) {
@@ -991,7 +990,7 @@ fun AppSettingsScreen(navController: NavController) {
checked = takeoverWhenRingingCall,
onCheckedChange = {
takeoverWhenRingingCall = it
- sharedPreferences.edit().putBoolean("takeover_when_ringing_call", it).apply()
+ sharedPreferences.edit { putBoolean("takeover_when_ringing_call", it)}
}
)
}
@@ -1004,7 +1003,7 @@ fun AppSettingsScreen(navController: NavController) {
interactionSource = remember { MutableInteractionSource() }
) {
takeoverWhenMediaStart = !takeoverWhenMediaStart
- sharedPreferences.edit().putBoolean("takeover_when_media_start", takeoverWhenMediaStart).apply()
+ sharedPreferences.edit { putBoolean("takeover_when_media_start", takeoverWhenMediaStart)}
},
verticalAlignment = Alignment.CenterVertically
) {
@@ -1032,7 +1031,7 @@ fun AppSettingsScreen(navController: NavController) {
checked = takeoverWhenMediaStart,
onCheckedChange = {
takeoverWhenMediaStart = it
- sharedPreferences.edit().putBoolean("takeover_when_media_start", it).apply()
+ sharedPreferences.edit { putBoolean("takeover_when_media_start", it)}
}
)
}
@@ -1126,7 +1125,10 @@ fun AppSettingsScreen(navController: NavController) {
interactionSource = remember { MutableInteractionSource() }
) {
useAlternateHeadTrackingPackets = !useAlternateHeadTrackingPackets
- sharedPreferences.edit().putBoolean("use_alternate_head_tracking_packets", useAlternateHeadTrackingPackets).apply()
+ sharedPreferences.edit {
+ putBoolean(
+ "use_alternate_head_tracking_packets",
+ useAlternateHeadTrackingPackets)}
},
verticalAlignment = Alignment.CenterVertically
) {
@@ -1154,7 +1156,7 @@ fun AppSettingsScreen(navController: NavController) {
checked = useAlternateHeadTrackingPackets,
onCheckedChange = {
useAlternateHeadTrackingPackets = it
- sharedPreferences.edit().putBoolean("use_alternate_head_tracking_packets", it).apply()
+ sharedPreferences.edit { putBoolean("use_alternate_head_tracking_packets", it)}
}
)
}
@@ -1348,7 +1350,7 @@ fun AppSettingsScreen(navController: NavController) {
}
val base64Value = Base64.encode(hexBytes)
- sharedPreferences.edit().putString(AACPManager.Companion.ProximityKeyType.IRK.name, base64Value).apply()
+ sharedPreferences.edit { putString(AACPManager.Companion.ProximityKeyType.IRK.name, base64Value)}
Toast.makeText(context, "IRK has been set successfully", Toast.LENGTH_SHORT).show()
showIrkDialog = false
@@ -1437,7 +1439,7 @@ fun AppSettingsScreen(navController: NavController) {
}
val base64Value = Base64.encode(hexBytes)
- sharedPreferences.edit().putString(AACPManager.Companion.ProximityKeyType.ENC_KEY.name, base64Value).apply()
+ sharedPreferences.edit { putString(AACPManager.Companion.ProximityKeyType.ENC_KEY.name, base64Value)}
Toast.makeText(context, "Encryption key has been set successfully", Toast.LENGTH_SHORT).show()
showEncKeyDialog = false
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt
index 6529cbe..94fff3c 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt
@@ -74,6 +74,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -344,11 +345,11 @@ fun DebugScreen(navController: NavController) {
val packetLogs = airPodsService?.packetLogsFlow?.collectAsState(emptySet())?.value ?: emptySet()
val shouldScrollToBottom = remember { mutableStateOf(true) }
- val refreshTrigger = remember { mutableStateOf(0) }
- LaunchedEffect(refreshTrigger.value) {
+ val refreshTrigger = remember { mutableIntStateOf(0) }
+ LaunchedEffect(refreshTrigger.intValue) {
while(true) {
delay(1000)
- refreshTrigger.value = refreshTrigger.value + 1
+ refreshTrigger.intValue = refreshTrigger.intValue + 1
}
}
@@ -361,7 +362,7 @@ fun DebugScreen(navController: NavController) {
Toast.makeText(context, "Packet copied to clipboard", Toast.LENGTH_SHORT).show()
}
- LaunchedEffect(packetLogs.size, refreshTrigger.value) {
+ LaunchedEffect(packetLogs.size, refreshTrigger.intValue) {
if (shouldScrollToBottom.value && packetLogs.isNotEmpty()) {
listState.animateScrollToItem(packetLogs.size - 1)
}
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt
index 8197c2d..5b77ebb 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt
@@ -20,7 +20,6 @@
package me.kavishdevar.librepods.screens
-import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -36,7 +35,6 @@ import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@@ -44,9 +42,8 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.remember
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.Alignment
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
@@ -64,7 +61,6 @@ import kotlinx.coroutines.launch
import me.kavishdevar.librepods.R
import me.kavishdevar.librepods.composables.AccessibilitySlider
import me.kavishdevar.librepods.services.ServiceManager
-
import kotlin.io.encoding.ExperimentalEncodingApi
@OptIn(ExperimentalMaterial3Api::class)
@@ -301,4 +297,4 @@ fun EqualizerSettingsScreen(
}
}
}
-}
\ No newline at end of file
+}
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt
index dc7a540..3459266 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt
@@ -84,6 +84,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.kavishdevar.librepods.R
import me.kavishdevar.librepods.utils.RadareOffsetFinder
+import androidx.core.content.edit
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -528,7 +529,7 @@ fun Onboarding(navController: NavController, activityContext: Context) {
onClick = {
showSkipDialog = false
RadareOffsetFinder.clearHookOffsets()
- sharedPreferences.edit().putBoolean("skip_setup", true).apply()
+ sharedPreferences.edit { putBoolean("skip_setup", true) }
navController.navigate("settings") {
popUpTo("onboarding") { inclusive = true }
}
@@ -665,6 +666,3 @@ fun OnboardingPreview() {
Onboarding(navController = NavController(LocalContext.current), activityContext = LocalContext.current)
}
-private suspend fun delay(timeMillis: Long) {
- kotlinx.coroutines.delay(timeMillis)
-}
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt
index 4d2f2c8..eb884c9 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt
@@ -66,6 +66,7 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.core.content.edit
import androidx.navigation.NavController
import me.kavishdevar.librepods.R
import me.kavishdevar.librepods.constants.StemAction
@@ -178,7 +179,7 @@ fun LongPress(navController: NavController, name: String) {
selected = longPressAction == StemAction.CYCLE_NOISE_CONTROL_MODES,
onClick = {
longPressAction = StemAction.CYCLE_NOISE_CONTROL_MODES
- sharedPreferences.edit().putString(prefKey, StemAction.CYCLE_NOISE_CONTROL_MODES.name).apply()
+ sharedPreferences.edit { putString(prefKey, StemAction.CYCLE_NOISE_CONTROL_MODES.name)}
},
isFirst = true,
isLast = false
@@ -189,7 +190,7 @@ fun LongPress(navController: NavController, name: String) {
selected = longPressAction == StemAction.DIGITAL_ASSISTANT,
onClick = {
longPressAction = StemAction.DIGITAL_ASSISTANT
- sharedPreferences.edit().putString(prefKey, StemAction.DIGITAL_ASSISTANT.name).apply()
+ sharedPreferences.edit { putString(prefKey, StemAction.DIGITAL_ASSISTANT.name)}
},
isFirst = false,
isLast = true
@@ -271,7 +272,9 @@ fun LongPressElement(name: String, enabled: Boolean = true, resourceId: Int, isF
it.identifier == AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE_CONFIGS
}?.value?.takeIf { it.isNotEmpty() }?.get(0)
- val savedByte = context.getSharedPreferences("settings", Context.MODE_PRIVATE).getInt("long_press_byte", 0b0101.toInt())
+ val savedByte = context.getSharedPreferences("settings", Context.MODE_PRIVATE).getInt("long_press_byte",
+ 0b0101
+ )
val byteValue = currentByteValue ?: (savedByte and 0xFF).toByte()
val isChecked = (byteValue.toInt() and bit) != 0
@@ -331,8 +334,8 @@ fun LongPressElement(name: String, enabled: Boolean = true, resourceId: Int, isF
updatedByte
)
- context.getSharedPreferences("settings", Context.MODE_PRIVATE).edit()
- .putInt("long_press_byte", newValue).apply()
+ context.getSharedPreferences("settings", Context.MODE_PRIVATE).edit {
+ putInt("long_press_byte", newValue)}
checked.value = false
Log.d("PressAndHoldSettingsScreen", "Updated: $name, enabled: false, byte: ${updatedByte.toInt() and 0xFF}, bits: ${Integer.toBinaryString(updatedByte.toInt() and 0xFF)}")
@@ -345,8 +348,9 @@ fun LongPressElement(name: String, enabled: Boolean = true, resourceId: Int, isF
updatedByte
)
- context.getSharedPreferences("settings", Context.MODE_PRIVATE).edit()
- .putInt("long_press_byte", newValue).apply()
+ context.getSharedPreferences("settings", Context.MODE_PRIVATE).edit {
+ putInt("long_press_byte", newValue)
+ }
checked.value = true
Log.d("PressAndHoldSettingsScreen", "Updated: $name, enabled: true, byte: ${updatedByte.toInt() and 0xFF}, bits: ${newValue.toString(2)}")
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt
index 9601e93..bcb4dc5 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt
@@ -69,6 +69,7 @@ import androidx.navigation.NavController
import me.kavishdevar.librepods.R
import me.kavishdevar.librepods.services.ServiceManager
import kotlin.io.encoding.ExperimentalEncodingApi
+import androidx.core.content.edit
@OptIn(ExperimentalMaterial3Api::class)
@@ -153,7 +154,7 @@ fun RenameScreen(navController: NavController) {
value = name.value,
onValueChange = {
name.value = it
- sharedPreferences.edit().putString("name", it.text).apply()
+ sharedPreferences.edit {putString("name", it.text)}
ServiceManager.getService()?.setName(it.text)
},
textStyle = TextStyle(
@@ -175,7 +176,7 @@ fun RenameScreen(navController: NavController) {
IconButton(
onClick = {
name.value = TextFieldValue("")
- sharedPreferences.edit().putString("name", "").apply()
+ sharedPreferences.edit { putString("name", "") }
ServiceManager.getService()?.setName("")
}
) {
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt
index 747ed32..64bf6ff 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt
@@ -27,7 +27,6 @@ import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
-import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
@@ -46,17 +45,12 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
-import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Delete
-import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
@@ -65,7 +59,6 @@ import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
@@ -91,10 +84,7 @@ import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -102,7 +92,6 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -199,7 +188,7 @@ fun TroubleshootingScreen(navController: NavController) {
val buttonBgColor = if (isSystemInDarkTheme()) Color(0xFF333333) else Color(0xFFDDDDDD)
var instructionText by remember { mutableStateOf("") }
- var isDarkTheme = isSystemInDarkTheme()
+ val isDarkTheme = isSystemInDarkTheme()
var mDensity by remember { mutableFloatStateOf(0f) }
LaunchedEffect(Unit) {
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt
index f2d3a9f..71374d6 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt
@@ -753,6 +753,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
}
}
+ @Suppress("unused")
fun cameraClosed() {
cameraActive = false
setupStemActions()
@@ -894,7 +895,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
this@AirPodsService,
(batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0),
IslandType.MOVED_TO_OTHER_DEVICE,
- reversed = reasonReverseTapped
+ reversed = true
)
}
if (!aacpManager.owns) {
@@ -909,12 +910,12 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
}
override fun onShowNearbyUI() {
- // showIsland(
- // this@AirPodsService,
- // (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0),
- // IslandType.MOVED_TO_OTHER_DEVICE,
- // reversed = false
- // )
+ showIsland(
+ this@AirPodsService,
+ (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0),
+ IslandType.MOVED_TO_OTHER_DEVICE,
+ reversed = false
+ )
}
override fun onDeviceMetadataReceived(deviceMetadata: ByteArray) {
@@ -1462,7 +1463,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
}
fun setBatteryMetadata() {
- device?.let {
+ device?.let { it ->
SystemApisUtils.setMetadata(
it,
it.METADATA_UNTETHERED_CASE_BATTERY,
@@ -1502,7 +1503,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
val componentName = ComponentName(this, BatteryWidget::class.java)
val widgetIds = appWidgetManager.getAppWidgetIds(componentName)
- val remoteViews = RemoteViews(packageName, R.layout.battery_widget).also {
+ val remoteViews = RemoteViews(packageName, R.layout.battery_widget).also { it ->
val openActivityIntent = PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
it.setOnClickPendingIntent(R.id.battery_widget, openActivityIntent)
@@ -1569,7 +1570,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
if (widgetMobileBatteryEnabled) View.VISIBLE else View.GONE
)
if (widgetMobileBatteryEnabled) {
- val batteryManager = getSystemService(BatteryManager::class.java)
+ val batteryManager = getSystemService(BatteryManager::class.java)
val batteryLevel =
batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
val charging =
@@ -1606,7 +1607,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
val appWidgetManager = AppWidgetManager.getInstance(this)
val componentName = ComponentName(this, NoiseControlWidget::class.java)
val widgetIds = appWidgetManager.getAppWidgetIds(componentName)
- val remoteViews = RemoteViews(packageName, R.layout.noise_control_widget).also {
+ val remoteViews = RemoteViews(packageName, R.layout.noise_control_widget).also { it ->
val ancStatus = ancNotification.status
val allowOffModeValue = aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION }
val allowOffMode = allowOffModeValue?.value?.takeIf { it.isNotEmpty() }?.get(0) == 0x01.toByte()
@@ -2198,7 +2199,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
Log.d("AirPodsService", macAddress)
sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", false) }
- device = getSystemService(BluetoothManager::class.java).adapter.bondedDevices.find {
+ device = getSystemService(BluetoothManager::class.java).adapter.bondedDevices.find {
it.address == macAddress
}
@@ -2335,7 +2336,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
setupStemActions()
while (socket.isConnected) {
- socket.let {
+ socket.let { it ->
val buffer = ByteArray(1024)
val bytesRead = it.inputStream.read(buffer)
var data: ByteArray
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt
index 0a43e93..342db7a 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt
@@ -423,7 +423,7 @@ class AACPManager {
)
Log.d(
TAG, "Control command list is now: ${
- controlCommandStatusList.joinToString(", ") {
+ controlCommandStatusList.joinToString(", ") { it ->
"${it.identifier.name} (${it.identifier.value.toHexString()}) - ${
it.value.joinToString(
" "
@@ -692,8 +692,8 @@ class AACPManager {
if (selfMacAddress.length != 17 || !selfMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}")) || targetMacAddress.length != 17 || !targetMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}"))) {
throw IllegalArgumentException("MAC address must be 6 bytes")
}
- Log.d(TAG, "SELFMAC: ${selfMacAddress}, TARGETMAC: ${targetMacAddress}")
- Log.d(TAG, "Sending Media Information packet to ${targetMacAddress}")
+ Log.d(TAG, "SELFMAC: ${selfMacAddress}, TARGETMAC: $targetMacAddress")
+ Log.d(TAG, "Sending Media Information packet to $targetMacAddress")
return sendDataPacket(createMediaInformationNewDevicePacket(selfMacAddress, targetMacAddress))
}
@@ -775,7 +775,7 @@ class AACPManager {
if (selfMacAddress.length != 17 || !selfMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}"))) {
throw IllegalArgumentException("MAC address must be 6 bytes")
}
- Log.d(TAG, "SELFMAC: ${selfMacAddress}")
+ Log.d(TAG, "SELFMAC: $selfMacAddress")
val targetMac = connectedDevices.find { it.mac != selfMacAddress }?.mac
Log.d(TAG, "Sending Media Information packet to ${targetMac ?: "unknown device"}")
return sendDataPacket(
@@ -842,7 +842,7 @@ class AACPManager {
fun createSmartRoutingShowUIPacket(targetMacAddress: String): ByteArray {
val opcode = byteArrayOf(Opcodes.SMART_ROUTING, 0x00)
- val buffer = ByteBuffer.allocate(134)
+ val buffer = ByteBuffer.allocate(134)
buffer.put(
targetMacAddress.split(":").map { it.toInt(16).toByte() }.toByteArray().reversedArray()
)
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/BLEManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/BLEManager.kt
index c62b24a..a047d02 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/utils/BLEManager.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/BLEManager.kt
@@ -30,7 +30,6 @@ import android.content.SharedPreferences
import android.os.Handler
import android.os.Looper
import android.util.Log
-import me.kavishdevar.librepods.services.ServiceManager
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import kotlin.io.encoding.Base64
@@ -223,12 +222,13 @@ class BLEManager(private val context: Context) {
}
}
+ @SuppressLint("GetInstance")
private fun decryptLastBytes(data: ByteArray, key: ByteArray): ByteArray? {
return try {
if (data.size < 16) {
return null
}
-
+
val block = data.copyOfRange(data.size - 16, data.size)
val cipher = Cipher.getInstance("AES/ECB/NoPadding")
val secretKey = SecretKeySpec(key, "AES")
@@ -302,7 +302,7 @@ class BLEManager(private val context: Context) {
if (previousGlobalState != parsedStatus.lidOpen) {
listener.onLidStateChanged(parsedStatus.lidOpen)
- Log.d(TAG, "Lid state changed from ${previousGlobalState} to ${parsedStatus.lidOpen}")
+ Log.d(TAG, "Lid state changed from $previousGlobalState to ${parsedStatus.lidOpen}")
}
}
@@ -348,13 +348,13 @@ class BLEManager(private val context: Context) {
val isRightInEar = if (xorFactor) (status and 0x02) != 0 else (status and 0x08) != 0
val isFlipped = !primaryLeft
-
+
val leftByteIndex = if (isFlipped) 2 else 1
val rightByteIndex = if (isFlipped) 1 else 2
-
+
val (isLeftCharging, leftBattery) = formatBattery(decrypted[leftByteIndex].toInt() and 0xFF)
val (isRightCharging, rightBattery) = formatBattery(decrypted[rightByteIndex].toInt() and 0xFF)
-
+
val rawCaseBatteryByte = decrypted[3].toInt() and 0xFF
val (isCaseCharging, rawCaseBattery) = formatBattery(rawCaseBatteryByte)
@@ -442,10 +442,10 @@ class BLEManager(private val context: Context) {
val isRightInEar = if (xorFactor) (status and 0x02) != 0 else (status and 0x08) != 0
val isFlipped = !primaryLeft
-
+
val leftBatteryNibble = if (isFlipped) (podsBattery shr 4) and 0x0F else podsBattery and 0x0F
val rightBatteryNibble = if (isFlipped) podsBattery and 0x0F else (podsBattery shr 4) and 0x0F
-
+
val caseBattery = flagsCase and 0x0F
val flags = (flagsCase shr 4) and 0x0F
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/BluetoothCryptography.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/BluetoothCryptography.kt
index 145c89f..4f0ed98 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/utils/BluetoothCryptography.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/BluetoothCryptography.kt
@@ -18,6 +18,7 @@
package me.kavishdevar.librepods.utils
+import android.annotation.SuppressLint
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
@@ -26,10 +27,10 @@ import javax.crypto.spec.SecretKeySpec
* verifying Resolvable Private Addresses (RPA) used by AirPods.
*/
object BluetoothCryptography {
-
+
/**
* Verifies if the provided Bluetooth address is an RPA that matches the given Identity Resolving Key (IRK)
- *
+ *
* @param addr The Bluetooth address to verify
* @param irk The Identity Resolving Key to use for verification
* @return true if the address is verified as an RPA matching the IRK
@@ -44,11 +45,12 @@ object BluetoothCryptography {
/**
* Performs E function (AES-128) as specified in Bluetooth Core Specification
- *
+ *
* @param key The key for encryption
* @param data The data to encrypt
* @return The encrypted data
*/
+ @SuppressLint("GetInstance")
fun e(key: ByteArray, data: ByteArray): ByteArray {
val swappedKey = key.reversedArray()
val swappedData = data.reversedArray()
@@ -60,7 +62,7 @@ object BluetoothCryptography {
/**
* Performs the ah function as specified in Bluetooth Core Specification
- *
+ *
* @param k The IRK key
* @param r The random part of the address
* @return The hash part of the address
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/CrossDevice.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/CrossDevice.kt
index f5130ea..8c9ee97 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/utils/CrossDevice.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/CrossDevice.kt
@@ -34,6 +34,7 @@ import android.content.Intent
import android.content.SharedPreferences
import android.os.ParcelUuid
import android.util.Log
+import androidx.core.content.edit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
@@ -76,7 +77,7 @@ object CrossDevice {
CoroutineScope(Dispatchers.IO).launch {
Log.d("CrossDevice", "Initializing CrossDevice")
sharedPreferences = context.getSharedPreferences("packet_logs", Context.MODE_PRIVATE)
- sharedPreferences.edit().putBoolean("CrossDeviceIsAvailable", false).apply()
+ sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", false)}
this@CrossDevice.bluetoothAdapter = context.getSystemService(BluetoothManager::class.java).adapter
this@CrossDevice.bluetoothLeAdvertiser = bluetoothAdapter.bluetoothLeAdvertiser
// startAdvertising()
@@ -111,7 +112,7 @@ object CrossDevice {
}
}
- @SuppressLint("MissingPermission")
+ @SuppressLint("MissingPermission", "unused")
private fun startAdvertising() {
CoroutineScope(Dispatchers.IO).launch {
val settings = AdvertiseSettings.Builder()
@@ -147,7 +148,7 @@ object CrossDevice {
fun setAirPodsConnected(connected: Boolean) {
if (connected) {
isAvailable = false
- sharedPreferences.edit().putBoolean("CrossDeviceIsAvailable", false).apply()
+ sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", false)}
clientSocket?.outputStream?.write(CrossDevicePackets.AIRPODS_CONNECTED.packet)
} else {
clientSocket?.outputStream?.write(CrossDevicePackets.AIRPODS_DISCONNECTED.packet)
@@ -168,7 +169,7 @@ object CrossDevice {
val logEntry = "$source: $packetHex"
val logs = sharedPreferences.getStringSet(PACKET_LOG_KEY, mutableSetOf())?.toMutableSet() ?: mutableSetOf()
logs.add(logEntry)
- sharedPreferences.edit().putStringSet(PACKET_LOG_KEY, logs).apply()
+ sharedPreferences.edit { putStringSet(PACKET_LOG_KEY, logs)}
}
@SuppressLint("MissingPermission")
@@ -207,10 +208,10 @@ object CrossDevice {
}
} else if (packet.contentEquals(CrossDevicePackets.AIRPODS_CONNECTED.packet)) {
isAvailable = true
- sharedPreferences.edit().putBoolean("CrossDeviceIsAvailable", true).apply()
+ sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", true)}
} else if (packet.contentEquals(CrossDevicePackets.AIRPODS_DISCONNECTED.packet)) {
isAvailable = false
- sharedPreferences.edit().putBoolean("CrossDeviceIsAvailable", false).apply()
+ sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", false)}
} else if (packet.contentEquals(CrossDevicePackets.REQUEST_BATTERY_BYTES.packet)) {
Log.d("CrossDevice", "Received battery request, battery data: ${batteryBytes.joinToString("") { "%02x".format(it) }}")
sendRemotePacket(batteryBytes)
@@ -223,7 +224,7 @@ object CrossDevice {
} else {
if (packet.sliceArray(0..3).contentEquals(CrossDevicePackets.AIRPODS_DATA_HEADER.packet)) {
isAvailable = true
- sharedPreferences.edit().putBoolean("CrossDeviceIsAvailable", true).apply()
+ sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", true) }
if (packet.size % 2 == 0) {
val half = packet.size / 2
if (packet.sliceArray(0 until half).contentEquals(packet.sliceArray(half until packet.size))) {
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt
index 769f04d..d8ef502 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt
@@ -113,7 +113,7 @@ class IslandWindow(private val context: Context) {
intent.getParcelableArrayListExtra("data", Battery::class.java)
} else {
@Suppress("DEPRECATION")
- intent.getParcelableArrayListExtra("data")
+ intent.getParcelableArrayListExtra("data")
}
updateBatteryDisplay(batteryList)
} else if (intent?.action == AirPodsNotifications.DISCONNECT_RECEIVERS) {
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/KotlinModule.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/KotlinModule.kt
index 1fc4d8a..e2d5046 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/utils/KotlinModule.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/KotlinModule.kt
@@ -1,5 +1,6 @@
package me.kavishdevar.librepods.utils
+import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
@@ -17,6 +18,7 @@ import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
+import androidx.core.net.toUri
import io.github.libxposed.api.XposedInterface
import io.github.libxposed.api.XposedInterface.AfterHookCallback
import io.github.libxposed.api.XposedModule
@@ -27,7 +29,7 @@ import io.github.libxposed.api.annotations.XposedHooker
private const val TAG = "AirPodsHook"
private lateinit var module: KotlinModule
-
+@SuppressLint("DiscouragedApi", "PrivateApi")
class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModule(base, param) {
init {
Log.i(TAG, "AirPodsHook module initialized at :: ${param.processName}")
@@ -60,7 +62,7 @@ class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModul
val updateIconMethod = headerControllerClass.getDeclaredMethod(
"updateIcon",
- android.widget.ImageView::class.java,
+ ImageView::class.java,
String::class.java)
hook(updateIconMethod, BluetoothIconHooker::class.java)
@@ -89,7 +91,7 @@ class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModul
val updateIconMethod = headerControllerClass.getDeclaredMethod(
"updateIcon",
- android.widget.ImageView::class.java,
+ ImageView::class.java,
String::class.java)
hook(updateIconMethod, BluetoothIconHooker::class.java)
@@ -209,7 +211,7 @@ class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModul
val imageView = callback.args[0] as ImageView
val iconUri = callback.args[1] as String
- val uri = android.net.Uri.parse(iconUri)
+ val uri = iconUri.toUri()
if (uri.toString().startsWith("android.resource://me.kavishdevar.librepods")) {
Log.i(TAG, "Handling AirPods icon URI: $uri")
@@ -571,10 +573,10 @@ class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModul
addView(icon)
- if (isSelected) {
- background = createSelectedBackground(context)
+ background = if (isSelected) {
+ createSelectedBackground(context)
} else {
- background = null
+ null
}
setOnClickListener {
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt
index 8ce65ab..cc59214 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt
@@ -19,8 +19,6 @@
package me.kavishdevar.librepods.utils
import android.content.Context
-import android.content.Intent
-import android.net.Uri
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.BufferedReader
@@ -30,7 +28,7 @@ import java.io.InputStreamReader
class LogCollector(private val context: Context) {
private var isCollecting = false
private var logProcess: Process? = null
-
+
suspend fun openXposedSettings(context: Context) {
withContext(Dispatchers.IO) {
val command = if (android.os.Build.VERSION.SDK_INT >= 29) {
@@ -38,42 +36,42 @@ class LogCollector(private val context: Context) {
} else {
"am broadcast -a android.provider.Telephony.SECRET_CODE -d android_secret_code://5776733 android"
}
-
+
executeRootCommand(command)
}
}
-
+
suspend fun clearLogs() {
withContext(Dispatchers.IO) {
executeRootCommand("logcat -c")
}
}
-
+
suspend fun killBluetoothService() {
withContext(Dispatchers.IO) {
executeRootCommand("killall com.android.bluetooth")
}
}
-
+
private suspend fun getPackageUIDs(): Pair {
return withContext(Dispatchers.IO) {
val btUid = executeRootCommand("dumpsys package com.android.bluetooth | grep -m 1 \"uid=\" | sed -E 's/.*uid=([0-9]+).*/\\1/'")
.trim()
.takeIf { it.isNotEmpty() }
-
+
val appUid = executeRootCommand("dumpsys package me.kavishdevar.librepods | grep -m 1 \"uid=\" | sed -E 's/.*uid=([0-9]+).*/\\1/'")
.trim()
.takeIf { it.isNotEmpty() }
-
+
Pair(btUid, appUid)
}
}
-
+
suspend fun startLogCollection(listener: (String) -> Unit, connectionDetectedCallback: () -> Unit): String {
return withContext(Dispatchers.IO) {
isCollecting = true
val (btUid, appUid) = getPackageUIDs()
-
+
val uidFilter = buildString {
if (!btUid.isNullOrEmpty() && !appUid.isNullOrEmpty()) {
append("$btUid,$appUid")
@@ -83,33 +81,33 @@ class LogCollector(private val context: Context) {
append(appUid)
}
}
-
+
val command = if (uidFilter.isNotEmpty()) {
"su -c logcat --uid=$uidFilter -v threadtime"
} else {
"su -c logcat -v threadtime"
}
-
+
val logs = StringBuilder()
try {
logProcess = Runtime.getRuntime().exec(command)
val reader = BufferedReader(InputStreamReader(logProcess!!.inputStream))
var line: String? = null
var connectionDetected = false
-
+
while (isCollecting && reader.readLine().also { line = it } != null) {
line?.let {
if (it.contains("")) {
connectionDetected = true
@@ -118,7 +116,7 @@ class LogCollector(private val context: Context) {
connectionDetected = true
connectionDetectedCallback()
} else if (it.contains("")) {
- }
+ }
else if (it.contains("AirPodsService") && it.contains("Connected to device")) {
connectionDetected = true
connectionDetectedCallback()
@@ -139,17 +137,17 @@ class LogCollector(private val context: Context) {
logs.append("Error collecting logs: ${e.message}").append("\n")
e.printStackTrace()
}
-
+
logs.toString()
}
}
-
+
fun stopLogCollection() {
isCollecting = false
logProcess?.destroy()
logProcess = null
}
-
+
suspend fun saveLogToInternalStorage(fileName: String, content: String): File? {
return withContext(Dispatchers.IO) {
try {
@@ -157,7 +155,7 @@ class LogCollector(private val context: Context) {
if (!logsDir.exists()) {
logsDir.mkdir()
}
-
+
val file = File(logsDir, fileName)
file.writeText(content)
return@withContext file
@@ -167,31 +165,31 @@ class LogCollector(private val context: Context) {
}
}
}
-
+
suspend fun addLogMarker(markerType: LogMarkerType, details: String = "") {
withContext(Dispatchers.IO) {
val timestamp = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", java.util.Locale.US)
.format(java.util.Date())
-
+
val marker = when (markerType) {
LogMarkerType.START -> " [$timestamp] Beginning connection test"
LogMarkerType.SUCCESS -> " [$timestamp] Connection test completed successfully"
LogMarkerType.FAILURE -> " [$timestamp] Connection test failed"
LogMarkerType.CUSTOM -> " [$timestamp]"
}
-
+
val command = "log -t AirPodsService \"$marker\""
executeRootCommand(command)
}
}
-
+
enum class LogMarkerType {
START,
SUCCESS,
FAILURE,
CUSTOM
}
-
+
private suspend fun executeRootCommand(command: String): String {
return withContext(Dispatchers.IO) {
try {
@@ -199,11 +197,11 @@ class LogCollector(private val context: Context) {
val reader = BufferedReader(InputStreamReader(process.inputStream))
val output = StringBuilder()
var line: String?
-
+
while (reader.readLine().also { line = it } != null) {
output.append(line).append("\n")
}
-
+
process.waitFor()
output.toString()
} catch (e: Exception) {
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt
index aa55c15..631673b 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt
@@ -275,7 +275,7 @@ object MediaController {
} else {
initialVolume!!
}
- smoothVolumeTransition(initialVolume!!, targetVolume.toInt())
+ smoothVolumeTransition(initialVolume!!, targetVolume)
if (conversationalAwarenessPauseMusic) {
sendPause(force = true)
}
@@ -311,4 +311,4 @@ object MediaController {
}
})
}
-}
\ No newline at end of file
+}
diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/PopupWindow.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/PopupWindow.kt
index d050cba..1d54aa9 100644
--- a/android/app/src/main/java/me/kavishdevar/librepods/utils/PopupWindow.kt
+++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/PopupWindow.kt
@@ -49,7 +49,6 @@ import me.kavishdevar.librepods.constants.AirPodsNotifications
import me.kavishdevar.librepods.constants.Battery
import me.kavishdevar.librepods.constants.BatteryComponent
import me.kavishdevar.librepods.constants.BatteryStatus
-import kotlin.collections.find
@SuppressLint("InflateParams", "ClickableViewAccessibility")
class PopupWindow(
@@ -172,7 +171,12 @@ class PopupWindow(
batteryUpdateReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == AirPodsNotifications.BATTERY_DATA) {
- val batteryList = intent.getParcelableArrayListExtra("data")
+ val batteryList = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableArrayListExtra("data", Battery::class.java)
+ } else {
+ @Suppress("DEPRECATION")
+ intent.getParcelableArrayListExtra("data")
+ }
if (batteryList != null) {
updateBatteryStatusFromList(batteryList)
}
@@ -272,7 +276,4 @@ class PopupWindow(
onCloseCallback()
}
}
-
- val isShowing: Boolean
- get() = mView.parent != null && !isClosing
}
diff --git a/android/app/src/main/res/drawable/app_widget_background.xml b/android/app/src/main/res/drawable/app_widget_background.xml
new file mode 100644
index 0000000..785445c
--- /dev/null
+++ b/android/app/src/main/res/drawable/app_widget_background.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/app_widget_inner_view_background.xml b/android/app/src/main/res/drawable/app_widget_inner_view_background.xml
new file mode 100644
index 0000000..11a09f9
--- /dev/null
+++ b/android/app/src/main/res/drawable/app_widget_inner_view_background.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/layout/island_window.xml b/android/app/src/main/res/layout/island_window.xml
index 5cb1e14..c804737 100644
--- a/android/app/src/main/res/layout/island_window.xml
+++ b/android/app/src/main/res/layout/island_window.xml
@@ -113,7 +113,7 @@
android:layout_gravity="center"
android:translationX="-12dp"
android:background="@drawable/ic_undo_button_bg"
- android:contentDescription="Undo button"
+ android:contentDescription="@string/undo"
android:scaleType="centerInside"
android:src="@drawable/ic_undo"
android:tint="@android:color/white"
@@ -121,4 +121,4 @@
android:translationZ="8dp"
android:visibility="gone" />
-
\ No newline at end of file
+
diff --git a/android/app/src/main/res/layout/noise_control_widget.xml b/android/app/src/main/res/layout/noise_control_widget.xml
index 6b53bb1..baa2f4b 100644
--- a/android/app/src/main/res/layout/noise_control_widget.xml
+++ b/android/app/src/main/res/layout/noise_control_widget.xml
@@ -4,12 +4,14 @@
android:id="@+id/noise_control_widget"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:theme="@style/Theme.LibrePods.AppWidgetContainer">
+ android:theme="@style/Theme.LibrePods.AppWidgetContainer"
+ tools:ignore="ContentDescription,NestedWeights">
+ android:textSize="12sp"
+ tools:ignore="NestedWeights" />
+ android:textSize="12sp"
+ tools:ignore="NestedWeights" />
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 0000000..8fde456
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/android/app/src/main/res/raw/blip_yes.wav b/android/app/src/main/res/raw/blip_yes.wav
index b796b59..e69de29 100644
Binary files a/android/app/src/main/res/raw/blip_yes.wav and b/android/app/src/main/res/raw/blip_yes.wav differ
diff --git a/android/app/src/main/res/values-v21/styles.xml b/android/app/src/main/res/values-v21/styles.xml
deleted file mode 100644
index 6d4e9a1..0000000
--- a/android/app/src/main/res/values-v21/styles.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 3c3413d..25458ed 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -48,7 +48,7 @@
Connected
Moved to Linux
Moved to other device
- Reconnect from notification
+ Reconnect from notification
Head Tracking
Nod to answer calls, and shake your head to decline.
General
@@ -81,4 +81,5 @@
Your phone starts ringing
Starting media playback
Your phone starts playing media
+ Undo
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index 30e8f43..df666a4 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -1,12 +1,6 @@
-
+
-
+