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 @@ - + - +