mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-11 22:07:16 +00:00
android: clean up a lot of stuff
This commit is contained in:
@@ -35,8 +35,6 @@
|
|||||||
<uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS"
|
<uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS"
|
||||||
tools:ignore="ProtectedPermissions" />
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
|
||||||
<protected-broadcast android:name="batterywidget.impl.action.update_bluetooth_data" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
package me.kavishdevar.librepods
|
package me.kavishdevar.librepods
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.bluetooth.BluetoothDevice
|
import android.bluetooth.BluetoothDevice
|
||||||
import android.bluetooth.BluetoothManager
|
import android.bluetooth.BluetoothManager
|
||||||
@@ -29,26 +28,12 @@ import android.util.Log
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.annotation.RequiresPermission
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
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.mutableStateOf
|
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.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
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.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -56,36 +41,40 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.withTimeout
|
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.io.IOException
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
|
@Suppress("PrivatePropertyName")
|
||||||
class CustomDevice : ComponentActivity() {
|
class CustomDevice : ComponentActivity() {
|
||||||
private val TAG = "AirPodsAccessibilitySettings"
|
private val TAG = "AirPodsAccessibilitySettings"
|
||||||
private var socket: BluetoothSocket? = null
|
private var socket: BluetoothSocket? = null
|
||||||
private val deviceAddress = "28:2D:7F:C2:05:5B"
|
private val deviceAddress = "28:2D:7F:C2:05:5B"
|
||||||
private val psm = 31
|
|
||||||
private val uuid: ParcelUuid = ParcelUuid.fromString("00000000-0000-0000-0000-00000000000")
|
private val uuid: ParcelUuid = ParcelUuid.fromString("00000000-0000-0000-0000-00000000000")
|
||||||
|
|
||||||
// Data states
|
// Data states
|
||||||
private val isConnected = mutableStateOf(false)
|
private val isConnected = mutableStateOf(false)
|
||||||
private val leftAmplification = mutableStateOf(1.0f)
|
private val leftAmplification = mutableFloatStateOf(1.0f)
|
||||||
private val leftTone = mutableStateOf(1.0f)
|
private val leftTone = mutableFloatStateOf(1.0f)
|
||||||
private val leftAmbientNoiseReduction = mutableStateOf(0.5f)
|
private val leftAmbientNoiseReduction = mutableFloatStateOf(0.5f)
|
||||||
private val leftConversationBoost = mutableStateOf(false)
|
private val leftConversationBoost = mutableStateOf(false)
|
||||||
private val leftEQ = mutableStateOf(FloatArray(8) { 50.0f })
|
private val leftEQ = mutableStateOf(FloatArray(8) { 50.0f })
|
||||||
|
|
||||||
private val rightAmplification = mutableStateOf(1.0f)
|
private val rightAmplification = mutableFloatStateOf(1.0f)
|
||||||
private val rightTone = mutableStateOf(1.0f)
|
private val rightTone = mutableFloatStateOf(1.0f)
|
||||||
private val rightAmbientNoiseReduction = mutableStateOf(0.5f)
|
private val rightAmbientNoiseReduction = mutableFloatStateOf(0.5f)
|
||||||
private val rightConversationBoost = mutableStateOf(false)
|
private val rightConversationBoost = mutableStateOf(false)
|
||||||
private val rightEQ = mutableStateOf(FloatArray(8) { 50.0f })
|
private val rightEQ = mutableStateOf(FloatArray(8) { 50.0f })
|
||||||
|
|
||||||
private val singleMode = mutableStateOf(false)
|
private val singleMode = mutableStateOf(false)
|
||||||
private val amplification = mutableStateOf(1.0f)
|
private val amplification = mutableFloatStateOf(1.0f)
|
||||||
private val balance = mutableStateOf(0.5f)
|
private val balance = mutableFloatStateOf(0.5f)
|
||||||
|
|
||||||
private val retryCount = mutableStateOf(0)
|
private val retryCount = mutableIntStateOf(0)
|
||||||
private val showRetryButton = mutableStateOf(false)
|
private val showRetryButton = mutableStateOf(false)
|
||||||
private val maxRetries = 3
|
private val maxRetries = 3
|
||||||
|
|
||||||
@@ -146,18 +135,19 @@ class CustomDevice : ComponentActivity() {
|
|||||||
socket?.close()
|
socket?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
private suspend fun connectL2CAP() {
|
private suspend fun connectL2CAP() {
|
||||||
retryCount.value = 0
|
retryCount.intValue = 0
|
||||||
// Close any existing socket
|
// Close any existing socket
|
||||||
socket?.close()
|
socket?.close()
|
||||||
socket = null
|
socket = null
|
||||||
while (retryCount.value < maxRetries) {
|
while (retryCount.intValue < maxRetries) {
|
||||||
try {
|
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;")
|
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
|
||||||
val manager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager
|
val manager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager
|
||||||
val device: BluetoothDevice = manager.adapter.getRemoteDevice(deviceAddress)
|
val device: BluetoothDevice = manager.adapter.getRemoteDevice(deviceAddress)
|
||||||
socket = createBluetoothSocket(device, psm)
|
socket = createBluetoothSocket(device)
|
||||||
|
|
||||||
withTimeout(5000L) {
|
withTimeout(5000L) {
|
||||||
socket?.connect()
|
socket?.connect()
|
||||||
@@ -177,9 +167,9 @@ class CustomDevice : ComponentActivity() {
|
|||||||
|
|
||||||
return
|
return
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to connect, attempt ${retryCount.value + 1}: ${e.message}")
|
Log.e(TAG, "Failed to connect, attempt ${retryCount.intValue + 1}: ${e.message}")
|
||||||
retryCount.value++
|
retryCount.intValue++
|
||||||
if (retryCount.value < maxRetries) {
|
if (retryCount.intValue < maxRetries) {
|
||||||
delay(2000) // Wait 2 seconds before retry
|
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 type = 3 // L2CAP
|
||||||
val constructorSpecs = listOf(
|
val constructorSpecs = listOf(
|
||||||
arrayOf(device, type, true, true, 31, uuid),
|
arrayOf(device, type, true, true, 31, uuid),
|
||||||
@@ -300,18 +290,18 @@ class CustomDevice : ComponentActivity() {
|
|||||||
leftEQ.value = newLeftEQ
|
leftEQ.value = newLeftEQ
|
||||||
if (singleMode.value) rightEQ.value = newLeftEQ
|
if (singleMode.value) rightEQ.value = newLeftEQ
|
||||||
|
|
||||||
leftAmplification.value = buffer.float
|
leftAmplification.floatValue = buffer.float
|
||||||
Log.d(TAG, "Parsed left amplification: ${leftAmplification.value}")
|
Log.d(TAG, "Parsed left amplification: ${leftAmplification.floatValue}")
|
||||||
leftTone.value = buffer.float
|
leftTone.floatValue = buffer.float
|
||||||
Log.d(TAG, "Parsed left tone: ${leftTone.value}")
|
Log.d(TAG, "Parsed left tone: ${leftTone.floatValue}")
|
||||||
if (singleMode.value) rightTone.value = leftTone.value
|
if (singleMode.value) rightTone.floatValue = leftTone.floatValue
|
||||||
val leftConvFloat = buffer.float
|
val leftConvFloat = buffer.float
|
||||||
leftConversationBoost.value = leftConvFloat > 0.5f
|
leftConversationBoost.value = leftConvFloat > 0.5f
|
||||||
Log.d(TAG, "Parsed left conversation boost: $leftConvFloat (${leftConversationBoost.value})")
|
Log.d(TAG, "Parsed left conversation boost: $leftConvFloat (${leftConversationBoost.value})")
|
||||||
if (singleMode.value) rightConversationBoost.value = leftConversationBoost.value
|
if (singleMode.value) rightConversationBoost.value = leftConversationBoost.value
|
||||||
leftAmbientNoiseReduction.value = buffer.float
|
leftAmbientNoiseReduction.floatValue = buffer.float
|
||||||
Log.d(TAG, "Parsed left ambient noise reduction: ${leftAmbientNoiseReduction.value}")
|
Log.d(TAG, "Parsed left ambient noise reduction: ${leftAmbientNoiseReduction.floatValue}")
|
||||||
if (singleMode.value) rightAmbientNoiseReduction.value = leftAmbientNoiseReduction.value
|
if (singleMode.value) rightAmbientNoiseReduction.floatValue = leftAmbientNoiseReduction.floatValue
|
||||||
|
|
||||||
// Right bud
|
// Right bud
|
||||||
val newRightEQ = rightEQ.value.copyOf()
|
val newRightEQ = rightEQ.value.copyOf()
|
||||||
@@ -321,24 +311,24 @@ class CustomDevice : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
rightEQ.value = newRightEQ
|
rightEQ.value = newRightEQ
|
||||||
|
|
||||||
rightAmplification.value = buffer.float
|
rightAmplification.floatValue = buffer.float
|
||||||
Log.d(TAG, "Parsed right amplification: ${rightAmplification.value}")
|
Log.d(TAG, "Parsed right amplification: ${rightAmplification.floatValue}")
|
||||||
rightTone.value = buffer.float
|
rightTone.floatValue = buffer.float
|
||||||
Log.d(TAG, "Parsed right tone: ${rightTone.value}")
|
Log.d(TAG, "Parsed right tone: ${rightTone.floatValue}")
|
||||||
val rightConvFloat = buffer.float
|
val rightConvFloat = buffer.float
|
||||||
rightConversationBoost.value = rightConvFloat > 0.5f
|
rightConversationBoost.value = rightConvFloat > 0.5f
|
||||||
Log.d(TAG, "Parsed right conversation boost: $rightConvFloat (${rightConversationBoost.value})")
|
Log.d(TAG, "Parsed right conversation boost: $rightConvFloat (${rightConversationBoost.value})")
|
||||||
rightAmbientNoiseReduction.value = buffer.float
|
rightAmbientNoiseReduction.floatValue = buffer.float
|
||||||
Log.d(TAG, "Parsed right ambient noise reduction: ${rightAmbientNoiseReduction.value}")
|
Log.d(TAG, "Parsed right ambient noise reduction: ${rightAmbientNoiseReduction.floatValue}")
|
||||||
|
|
||||||
Log.d(TAG, "Settings parsed successfully")
|
Log.d(TAG, "Settings parsed successfully")
|
||||||
|
|
||||||
// Update single mode values if in single mode
|
// Update single mode values if in single mode
|
||||||
if (singleMode.value) {
|
if (singleMode.value) {
|
||||||
val avg = (leftAmplification.value + rightAmplification.value) / 2
|
val avg = (leftAmplification.floatValue + rightAmplification.floatValue) / 2
|
||||||
amplification.value = avg.coerceIn(0f, 1f)
|
amplification.floatValue = avg.coerceIn(0f, 1f)
|
||||||
val diff = rightAmplification.value - leftAmplification.value
|
val diff = rightAmplification.floatValue - leftAmplification.floatValue
|
||||||
balance.value = (0.5f + diff / (2 * avg)).coerceIn(0f, 1f)
|
balance.floatValue = (0.5f + diff / (2 * avg)).coerceIn(0f, 1f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,19 +353,19 @@ class CustomDevice : ComponentActivity() {
|
|||||||
for (eq in leftEQ.value) {
|
for (eq in leftEQ.value) {
|
||||||
buffer.putFloat(eq)
|
buffer.putFloat(eq)
|
||||||
}
|
}
|
||||||
buffer.putFloat(leftAmplification.value)
|
buffer.putFloat(leftAmplification.floatValue)
|
||||||
buffer.putFloat(leftTone.value)
|
buffer.putFloat(leftTone.floatValue)
|
||||||
buffer.putFloat(if (leftConversationBoost.value) 1.0f else 0.0f)
|
buffer.putFloat(if (leftConversationBoost.value) 1.0f else 0.0f)
|
||||||
buffer.putFloat(leftAmbientNoiseReduction.value)
|
buffer.putFloat(leftAmbientNoiseReduction.floatValue)
|
||||||
|
|
||||||
// Right bud
|
// Right bud
|
||||||
for (eq in rightEQ.value) {
|
for (eq in rightEQ.value) {
|
||||||
buffer.putFloat(eq)
|
buffer.putFloat(eq)
|
||||||
}
|
}
|
||||||
buffer.putFloat(rightAmplification.value)
|
buffer.putFloat(rightAmplification.floatValue)
|
||||||
buffer.putFloat(rightTone.value)
|
buffer.putFloat(rightTone.floatValue)
|
||||||
buffer.putFloat(if (rightConversationBoost.value) 1.0f else 0.0f)
|
buffer.putFloat(if (rightConversationBoost.value) 1.0f else 0.0f)
|
||||||
buffer.putFloat(rightAmbientNoiseReduction.value)
|
buffer.putFloat(rightAmbientNoiseReduction.floatValue)
|
||||||
|
|
||||||
val packet = buffer.array()
|
val packet = buffer.array()
|
||||||
Log.d(TAG, "Packet length: ${packet.size}")
|
Log.d(TAG, "Packet length: ${packet.size}")
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
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.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
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.MultiplePermissionsState
|
||||||
import com.google.accompanist.permissions.isGranted
|
import com.google.accompanist.permissions.isGranted
|
||||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||||
|
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||||
import me.kavishdevar.librepods.constants.AirPodsNotifications
|
import me.kavishdevar.librepods.constants.AirPodsNotifications
|
||||||
import me.kavishdevar.librepods.screens.AirPodsSettingsScreen
|
import me.kavishdevar.librepods.screens.AirPodsSettingsScreen
|
||||||
import me.kavishdevar.librepods.screens.AppSettingsScreen
|
import me.kavishdevar.librepods.screens.AppSettingsScreen
|
||||||
@@ -123,6 +126,7 @@ import kotlin.io.encoding.ExperimentalEncodingApi
|
|||||||
lateinit var serviceConnection: ServiceConnection
|
lateinit var serviceConnection: ServiceConnection
|
||||||
lateinit var connectionStatusReceiver: BroadcastReceiver
|
lateinit var connectionStatusReceiver: BroadcastReceiver
|
||||||
|
|
||||||
|
@ExperimentalHazeMaterialsApi
|
||||||
@ExperimentalMaterial3Api
|
@ExperimentalMaterial3Api
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -137,8 +141,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
LibrePodsTheme {
|
LibrePodsTheme {
|
||||||
getSharedPreferences("settings", MODE_PRIVATE).edit().putLong("textColor",
|
getSharedPreferences("settings", MODE_PRIVATE).edit {
|
||||||
MaterialTheme.colorScheme.onSurface.toArgb().toLong()).apply()
|
putLong(
|
||||||
|
"textColor",
|
||||||
|
MaterialTheme.colorScheme.onSurface.toArgb().toLong())}
|
||||||
Main()
|
Main()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,8 +213,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAddMagicKeys(uri: Uri) {
|
private fun handleAddMagicKeys(uri: Uri) {
|
||||||
val context = this
|
val sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
val sharedPreferences = getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
|
|
||||||
val irkHex = uri.getQueryParameter("irk")
|
val irkHex = uri.getQueryParameter("irk")
|
||||||
val encKeyHex = uri.getQueryParameter("enc_key")
|
val encKeyHex = uri.getQueryParameter("enc_key")
|
||||||
@@ -217,13 +222,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
if (irkHex != null && validateHexInput(irkHex)) {
|
if (irkHex != null && validateHexInput(irkHex)) {
|
||||||
val irkBytes = hexStringToByteArray(irkHex)
|
val irkBytes = hexStringToByteArray(irkHex)
|
||||||
val irkBase64 = Base64.encode(irkBytes)
|
val irkBase64 = Base64.encode(irkBytes)
|
||||||
sharedPreferences.edit().putString("IRK", irkBase64).apply()
|
sharedPreferences.edit {putString("IRK", irkBase64)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encKeyHex != null && validateHexInput(encKeyHex)) {
|
if (encKeyHex != null && validateHexInput(encKeyHex)) {
|
||||||
val encKeyBytes = hexStringToByteArray(encKeyHex)
|
val encKeyBytes = hexStringToByteArray(encKeyHex)
|
||||||
val encKeyBase64 = Base64.encode(encKeyBytes)
|
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()
|
Toast.makeText(this, "Magic keys added successfully!", Toast.LENGTH_SHORT).show()
|
||||||
@@ -247,6 +252,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExperimentalHazeMaterialsApi
|
||||||
@SuppressLint("MissingPermission", "InlinedApi", "UnspecifiedRegisterReceiverFlag")
|
@SuppressLint("MissingPermission", "InlinedApi", "UnspecifiedRegisterReceiverFlag")
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -404,6 +410,7 @@ fun Main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExperimentalHazeMaterialsApi
|
||||||
@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun PermissionsScreen(
|
fun PermissionsScreen(
|
||||||
@@ -586,7 +593,7 @@ fun PermissionsScreen(
|
|||||||
onClick = {
|
onClick = {
|
||||||
val intent = Intent(
|
val intent = Intent(
|
||||||
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||||
Uri.parse("package:${context.packageName}")
|
"package:${context.packageName}".toUri()
|
||||||
)
|
)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
onOverlaySettingsReturn()
|
onOverlaySettingsReturn()
|
||||||
@@ -616,9 +623,9 @@ fun PermissionsScreen(
|
|||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
val editor = context.getSharedPreferences("settings", MODE_PRIVATE).edit()
|
context.getSharedPreferences("settings", MODE_PRIVATE).edit {
|
||||||
editor.putBoolean("overlay_permission_skipped", true)
|
putBoolean("overlay_permission_skipped", true)
|
||||||
editor.apply()
|
}
|
||||||
|
|
||||||
val intent = Intent(context, MainActivity::class.java)
|
val intent = Intent(context, MainActivity::class.java)
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class QuickSettingsDialogActivity : ComponentActivity() {
|
|||||||
window.setGravity(Gravity.BOTTOM)
|
window.setGravity(Gravity.BOTTOM)
|
||||||
|
|
||||||
Intent(this, AirPodsService::class.java).also { intent ->
|
Intent(this, AirPodsService::class.java).also { intent ->
|
||||||
bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
bindService(intent, connection, BIND_AUTO_CREATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ fun AccessibilitySettings() {
|
|||||||
textColor = textColor
|
textColor = textColor
|
||||||
)
|
)
|
||||||
|
|
||||||
val volumeSwipeSpeedOptions = mapOf<Byte, String>(
|
val volumeSwipeSpeedOptions = mapOf(
|
||||||
1.toByte() to "Default",
|
1.toByte() to "Default",
|
||||||
2.toByte() to "Longer",
|
2.toByte() to "Longer",
|
||||||
3.toByte() to "Longest"
|
3.toByte() to "Longest"
|
||||||
|
|||||||
@@ -23,10 +23,8 @@ import androidx.compose.foundation.isSystemInDarkTheme
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) {
|
|||||||
batteryStatus.value = service.getBattery()
|
batteryStatus.value = service.getBattery()
|
||||||
|
|
||||||
if (preview) {
|
if (preview) {
|
||||||
batteryStatus.value = listOf<Battery>(
|
batteryStatus.value = listOf(
|
||||||
Battery(BatteryComponent.LEFT, 100, BatteryStatus.CHARGING),
|
Battery(BatteryComponent.LEFT, 100, BatteryStatus.CHARGING),
|
||||||
Battery(BatteryComponent.RIGHT, 50, BatteryStatus.NOT_CHARGING),
|
Battery(BatteryComponent.RIGHT, 50, BatteryStatus.NOT_CHARGING),
|
||||||
Battery(BatteryComponent.CASE, 5, BatteryStatus.CHARGING)
|
Battery(BatteryComponent.CASE, 5, BatteryStatus.CHARGING)
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package me.kavishdevar.librepods.composables
|
package me.kavishdevar.librepods.composables
|
||||||
|
|
||||||
import androidx.compose.animation.animateColorAsState
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import androidx.compose.ui.unit.sp
|
|||||||
import me.kavishdevar.librepods.services.AirPodsService
|
import me.kavishdevar.librepods.services.AirPodsService
|
||||||
import me.kavishdevar.librepods.utils.AACPManager
|
import me.kavishdevar.librepods.utils.AACPManager
|
||||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun IndependentToggle(name: String, service: AirPodsService? = null, functionName: String? = null, sharedPreferences: SharedPreferences, default: Boolean = false, controlCommandIdentifier: AACPManager.Companion.ControlCommandIdentifiers? = null) {
|
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() {
|
fun cb() {
|
||||||
if (controlCommandIdentifier == null) {
|
if (controlCommandIdentifier == null) {
|
||||||
sharedPreferences.edit().putBoolean(snakeCasedName, checked).apply()
|
sharedPreferences.edit { putBoolean(snakeCasedName, checked) }
|
||||||
}
|
}
|
||||||
if (functionName != null && service != null) {
|
if (functionName != null && service != null) {
|
||||||
val method =
|
val method =
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ fun isHeadTrackingData(data: ByteArray): Boolean {
|
|||||||
)
|
)
|
||||||
|
|
||||||
for (i in prefixPattern.indices) {
|
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
|
if (data[10] != 0x44.toByte() && data[10] != 0x45.toByte()) return false
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
package me.kavishdevar.librepods.constants
|
package me.kavishdevar.librepods.constants
|
||||||
|
|
||||||
import me.kavishdevar.librepods.constants.StemAction.entries
|
|
||||||
import me.kavishdevar.librepods.utils.AACPManager
|
import me.kavishdevar.librepods.utils.AACPManager
|
||||||
|
|
||||||
enum class StemAction {
|
enum class StemAction {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
package me.kavishdevar.librepods.screens
|
package me.kavishdevar.librepods.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
|||||||
@@ -57,8 +57,6 @@ import androidx.compose.material3.Slider
|
|||||||
import androidx.compose.material3.SliderDefaults
|
import androidx.compose.material3.SliderDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TextField
|
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
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.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import dev.chrisbanes.haze.HazeEffectScope
|
import dev.chrisbanes.haze.HazeEffectScope
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
@@ -190,7 +189,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
// Ensure the default value is properly set if not exists
|
// Ensure the default value is properly set if not exists
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (!sharedPreferences.contains("ble_only_mode")) {
|
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() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
showPhoneBatteryInWidget = !showPhoneBatteryInWidget
|
showPhoneBatteryInWidget = !showPhoneBatteryInWidget
|
||||||
sharedPreferences.edit().putBoolean("show_phone_battery_in_widget", showPhoneBatteryInWidget).apply()
|
sharedPreferences.edit { putBoolean("show_phone_battery_in_widget", showPhoneBatteryInWidget)}
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -340,7 +339,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
checked = showPhoneBatteryInWidget,
|
checked = showPhoneBatteryInWidget,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
showPhoneBatteryInWidget = it
|
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() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
bleOnlyMode = !bleOnlyMode
|
bleOnlyMode = !bleOnlyMode
|
||||||
sharedPreferences.edit().putBoolean("ble_only_mode", bleOnlyMode).apply()
|
sharedPreferences.edit { putBoolean("ble_only_mode", bleOnlyMode)}
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -403,7 +402,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
checked = bleOnlyMode,
|
checked = bleOnlyMode,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
bleOnlyMode = it
|
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) {
|
fun updateConversationalAwarenessPauseMusic(enabled: Boolean) {
|
||||||
conversationalAwarenessPauseMusicEnabled = enabled
|
conversationalAwarenessPauseMusicEnabled = enabled
|
||||||
sharedPreferences.edit().putBoolean("conversational_awareness_pause_music", enabled).apply()
|
sharedPreferences.edit { putBoolean("conversational_awareness_pause_music", enabled)}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateRelativeConversationalAwarenessVolume(enabled: Boolean) {
|
fun updateRelativeConversationalAwarenessVolume(enabled: Boolean) {
|
||||||
relativeConversationalAwarenessVolumeEnabled = enabled
|
relativeConversationalAwarenessVolumeEnabled = enabled
|
||||||
sharedPreferences.edit().putBoolean("relative_conversational_awareness_volume", enabled).apply()
|
sharedPreferences.edit { putBoolean("relative_conversational_awareness_volume", enabled)}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -541,7 +540,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
value = sliderValue.floatValue,
|
value = sliderValue.floatValue,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
sliderValue.floatValue = it
|
sliderValue.floatValue = it
|
||||||
sharedPreferences.edit().putInt("conversational_awareness_volume", it.toInt()).apply()
|
sharedPreferences.edit { putInt("conversational_awareness_volume", it.toInt())}
|
||||||
},
|
},
|
||||||
valueRange = 10f..85f,
|
valueRange = 10f..85f,
|
||||||
onValueChangeFinished = {
|
onValueChangeFinished = {
|
||||||
@@ -639,7 +638,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
) {
|
) {
|
||||||
fun updateQsClickBehavior(enabled: Boolean) {
|
fun updateQsClickBehavior(enabled: Boolean) {
|
||||||
openDialogForControlling = enabled
|
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(
|
Row(
|
||||||
@@ -708,7 +707,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
) {
|
) {
|
||||||
fun updateDisconnectWhenNotWearing(enabled: Boolean) {
|
fun updateDisconnectWhenNotWearing(enabled: Boolean) {
|
||||||
disconnectWhenNotWearing = enabled
|
disconnectWhenNotWearing = enabled
|
||||||
sharedPreferences.edit().putBoolean("disconnect_when_not_wearing", enabled).apply()
|
sharedPreferences.edit { putBoolean("disconnect_when_not_wearing", enabled)}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -789,7 +788,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
interactionSource = remember { MutableInteractionSource() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
takeoverWhenDisconnected = !takeoverWhenDisconnected
|
takeoverWhenDisconnected = !takeoverWhenDisconnected
|
||||||
sharedPreferences.edit().putBoolean("takeover_when_disconnected", takeoverWhenDisconnected).apply()
|
sharedPreferences.edit { putBoolean("takeover_when_disconnected", takeoverWhenDisconnected)}
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -817,7 +816,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
checked = takeoverWhenDisconnected,
|
checked = takeoverWhenDisconnected,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
takeoverWhenDisconnected = it
|
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() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
takeoverWhenIdle = !takeoverWhenIdle
|
takeoverWhenIdle = !takeoverWhenIdle
|
||||||
sharedPreferences.edit().putBoolean("takeover_when_idle", takeoverWhenIdle).apply()
|
sharedPreferences.edit { putBoolean("takeover_when_idle", takeoverWhenIdle)}
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -858,7 +857,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
checked = takeoverWhenIdle,
|
checked = takeoverWhenIdle,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
takeoverWhenIdle = it
|
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() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
takeoverWhenMusic = !takeoverWhenMusic
|
takeoverWhenMusic = !takeoverWhenMusic
|
||||||
sharedPreferences.edit().putBoolean("takeover_when_music", takeoverWhenMusic).apply()
|
sharedPreferences.edit { putBoolean("takeover_when_music", takeoverWhenMusic)}
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -899,7 +898,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
checked = takeoverWhenMusic,
|
checked = takeoverWhenMusic,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
takeoverWhenMusic = it
|
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() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
takeoverWhenCall = !takeoverWhenCall
|
takeoverWhenCall = !takeoverWhenCall
|
||||||
sharedPreferences.edit().putBoolean("takeover_when_call", takeoverWhenCall).apply()
|
sharedPreferences.edit { putBoolean("takeover_when_call", takeoverWhenCall)}
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -940,7 +939,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
checked = takeoverWhenCall,
|
checked = takeoverWhenCall,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
takeoverWhenCall = it
|
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() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
takeoverWhenRingingCall = !takeoverWhenRingingCall
|
takeoverWhenRingingCall = !takeoverWhenRingingCall
|
||||||
sharedPreferences.edit().putBoolean("takeover_when_ringing_call", takeoverWhenRingingCall).apply()
|
sharedPreferences.edit { putBoolean("takeover_when_ringing_call", takeoverWhenRingingCall)}
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -991,7 +990,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
checked = takeoverWhenRingingCall,
|
checked = takeoverWhenRingingCall,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
takeoverWhenRingingCall = it
|
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() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
takeoverWhenMediaStart = !takeoverWhenMediaStart
|
takeoverWhenMediaStart = !takeoverWhenMediaStart
|
||||||
sharedPreferences.edit().putBoolean("takeover_when_media_start", takeoverWhenMediaStart).apply()
|
sharedPreferences.edit { putBoolean("takeover_when_media_start", takeoverWhenMediaStart)}
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -1032,7 +1031,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
checked = takeoverWhenMediaStart,
|
checked = takeoverWhenMediaStart,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
takeoverWhenMediaStart = it
|
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() }
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
) {
|
) {
|
||||||
useAlternateHeadTrackingPackets = !useAlternateHeadTrackingPackets
|
useAlternateHeadTrackingPackets = !useAlternateHeadTrackingPackets
|
||||||
sharedPreferences.edit().putBoolean("use_alternate_head_tracking_packets", useAlternateHeadTrackingPackets).apply()
|
sharedPreferences.edit {
|
||||||
|
putBoolean(
|
||||||
|
"use_alternate_head_tracking_packets",
|
||||||
|
useAlternateHeadTrackingPackets)}
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -1154,7 +1156,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
checked = useAlternateHeadTrackingPackets,
|
checked = useAlternateHeadTrackingPackets,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
useAlternateHeadTrackingPackets = it
|
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)
|
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()
|
Toast.makeText(context, "IRK has been set successfully", Toast.LENGTH_SHORT).show()
|
||||||
showIrkDialog = false
|
showIrkDialog = false
|
||||||
@@ -1437,7 +1439,7 @@ fun AppSettingsScreen(navController: NavController) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val base64Value = Base64.encode(hexBytes)
|
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()
|
Toast.makeText(context, "Encryption key has been set successfully", Toast.LENGTH_SHORT).show()
|
||||||
showEncKeyDialog = false
|
showEncKeyDialog = false
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -344,11 +345,11 @@ fun DebugScreen(navController: NavController) {
|
|||||||
val packetLogs = airPodsService?.packetLogsFlow?.collectAsState(emptySet())?.value ?: emptySet()
|
val packetLogs = airPodsService?.packetLogsFlow?.collectAsState(emptySet())?.value ?: emptySet()
|
||||||
val shouldScrollToBottom = remember { mutableStateOf(true) }
|
val shouldScrollToBottom = remember { mutableStateOf(true) }
|
||||||
|
|
||||||
val refreshTrigger = remember { mutableStateOf(0) }
|
val refreshTrigger = remember { mutableIntStateOf(0) }
|
||||||
LaunchedEffect(refreshTrigger.value) {
|
LaunchedEffect(refreshTrigger.intValue) {
|
||||||
while(true) {
|
while(true) {
|
||||||
delay(1000)
|
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()
|
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()) {
|
if (shouldScrollToBottom.value && packetLogs.isNotEmpty()) {
|
||||||
listState.animateScrollToItem(packetLogs.size - 1)
|
listState.animateScrollToItem(packetLogs.size - 1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
|
|
||||||
package me.kavishdevar.librepods.screens
|
package me.kavishdevar.librepods.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -36,7 +35,6 @@ import androidx.compose.material3.Card
|
|||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@@ -44,9 +42,8 @@ import androidx.compose.material3.TopAppBar
|
|||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -64,7 +61,6 @@ import kotlinx.coroutines.launch
|
|||||||
import me.kavishdevar.librepods.R
|
import me.kavishdevar.librepods.R
|
||||||
import me.kavishdevar.librepods.composables.AccessibilitySlider
|
import me.kavishdevar.librepods.composables.AccessibilitySlider
|
||||||
import me.kavishdevar.librepods.services.ServiceManager
|
import me.kavishdevar.librepods.services.ServiceManager
|
||||||
|
|
||||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import me.kavishdevar.librepods.R
|
import me.kavishdevar.librepods.R
|
||||||
import me.kavishdevar.librepods.utils.RadareOffsetFinder
|
import me.kavishdevar.librepods.utils.RadareOffsetFinder
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -528,7 +529,7 @@ fun Onboarding(navController: NavController, activityContext: Context) {
|
|||||||
onClick = {
|
onClick = {
|
||||||
showSkipDialog = false
|
showSkipDialog = false
|
||||||
RadareOffsetFinder.clearHookOffsets()
|
RadareOffsetFinder.clearHookOffsets()
|
||||||
sharedPreferences.edit().putBoolean("skip_setup", true).apply()
|
sharedPreferences.edit { putBoolean("skip_setup", true) }
|
||||||
navController.navigate("settings") {
|
navController.navigate("settings") {
|
||||||
popUpTo("onboarding") { inclusive = true }
|
popUpTo("onboarding") { inclusive = true }
|
||||||
}
|
}
|
||||||
@@ -665,6 +666,3 @@ fun OnboardingPreview() {
|
|||||||
Onboarding(navController = NavController(LocalContext.current), activityContext = LocalContext.current)
|
Onboarding(navController = NavController(LocalContext.current), activityContext = LocalContext.current)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun delay(timeMillis: Long) {
|
|
||||||
kotlinx.coroutines.delay(timeMillis)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ import androidx.compose.ui.text.font.FontFamily
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import me.kavishdevar.librepods.R
|
import me.kavishdevar.librepods.R
|
||||||
import me.kavishdevar.librepods.constants.StemAction
|
import me.kavishdevar.librepods.constants.StemAction
|
||||||
@@ -178,7 +179,7 @@ fun LongPress(navController: NavController, name: String) {
|
|||||||
selected = longPressAction == StemAction.CYCLE_NOISE_CONTROL_MODES,
|
selected = longPressAction == StemAction.CYCLE_NOISE_CONTROL_MODES,
|
||||||
onClick = {
|
onClick = {
|
||||||
longPressAction = StemAction.CYCLE_NOISE_CONTROL_MODES
|
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,
|
isFirst = true,
|
||||||
isLast = false
|
isLast = false
|
||||||
@@ -189,7 +190,7 @@ fun LongPress(navController: NavController, name: String) {
|
|||||||
selected = longPressAction == StemAction.DIGITAL_ASSISTANT,
|
selected = longPressAction == StemAction.DIGITAL_ASSISTANT,
|
||||||
onClick = {
|
onClick = {
|
||||||
longPressAction = StemAction.DIGITAL_ASSISTANT
|
longPressAction = StemAction.DIGITAL_ASSISTANT
|
||||||
sharedPreferences.edit().putString(prefKey, StemAction.DIGITAL_ASSISTANT.name).apply()
|
sharedPreferences.edit { putString(prefKey, StemAction.DIGITAL_ASSISTANT.name)}
|
||||||
},
|
},
|
||||||
isFirst = false,
|
isFirst = false,
|
||||||
isLast = true
|
isLast = true
|
||||||
@@ -271,7 +272,9 @@ fun LongPressElement(name: String, enabled: Boolean = true, resourceId: Int, isF
|
|||||||
it.identifier == AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE_CONFIGS
|
it.identifier == AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE_CONFIGS
|
||||||
}?.value?.takeIf { it.isNotEmpty() }?.get(0)
|
}?.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 byteValue = currentByteValue ?: (savedByte and 0xFF).toByte()
|
||||||
|
|
||||||
val isChecked = (byteValue.toInt() and bit) != 0
|
val isChecked = (byteValue.toInt() and bit) != 0
|
||||||
@@ -331,8 +334,8 @@ fun LongPressElement(name: String, enabled: Boolean = true, resourceId: Int, isF
|
|||||||
updatedByte
|
updatedByte
|
||||||
)
|
)
|
||||||
|
|
||||||
context.getSharedPreferences("settings", Context.MODE_PRIVATE).edit()
|
context.getSharedPreferences("settings", Context.MODE_PRIVATE).edit {
|
||||||
.putInt("long_press_byte", newValue).apply()
|
putInt("long_press_byte", newValue)}
|
||||||
|
|
||||||
checked.value = false
|
checked.value = false
|
||||||
Log.d("PressAndHoldSettingsScreen", "Updated: $name, enabled: false, byte: ${updatedByte.toInt() and 0xFF}, bits: ${Integer.toBinaryString(updatedByte.toInt() and 0xFF)}")
|
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
|
updatedByte
|
||||||
)
|
)
|
||||||
|
|
||||||
context.getSharedPreferences("settings", Context.MODE_PRIVATE).edit()
|
context.getSharedPreferences("settings", Context.MODE_PRIVATE).edit {
|
||||||
.putInt("long_press_byte", newValue).apply()
|
putInt("long_press_byte", newValue)
|
||||||
|
}
|
||||||
|
|
||||||
checked.value = true
|
checked.value = true
|
||||||
Log.d("PressAndHoldSettingsScreen", "Updated: $name, enabled: true, byte: ${updatedByte.toInt() and 0xFF}, bits: ${newValue.toString(2)}")
|
Log.d("PressAndHoldSettingsScreen", "Updated: $name, enabled: true, byte: ${updatedByte.toInt() and 0xFF}, bits: ${newValue.toString(2)}")
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ import androidx.navigation.NavController
|
|||||||
import me.kavishdevar.librepods.R
|
import me.kavishdevar.librepods.R
|
||||||
import me.kavishdevar.librepods.services.ServiceManager
|
import me.kavishdevar.librepods.services.ServiceManager
|
||||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -153,7 +154,7 @@ fun RenameScreen(navController: NavController) {
|
|||||||
value = name.value,
|
value = name.value,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
name.value = it
|
name.value = it
|
||||||
sharedPreferences.edit().putString("name", it.text).apply()
|
sharedPreferences.edit {putString("name", it.text)}
|
||||||
ServiceManager.getService()?.setName(it.text)
|
ServiceManager.getService()?.setName(it.text)
|
||||||
},
|
},
|
||||||
textStyle = TextStyle(
|
textStyle = TextStyle(
|
||||||
@@ -175,7 +176,7 @@ fun RenameScreen(navController: NavController) {
|
|||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
name.value = TextFieldValue("")
|
name.value = TextFieldValue("")
|
||||||
sharedPreferences.edit().putString("name", "").apply()
|
sharedPreferences.edit { putString("name", "") }
|
||||||
ServiceManager.getService()?.setName("")
|
ServiceManager.getService()?.setName("")
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import androidx.compose.animation.core.Spring
|
|||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.spring
|
import androidx.compose.animation.core.spring
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.expandVertically
|
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.slideInVertically
|
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.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
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.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
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.Delete
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
|
||||||
import androidx.compose.material.icons.filled.Share
|
import androidx.compose.material.icons.filled.Share
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
@@ -65,7 +59,6 @@ import androidx.compose.material3.CenterAlignedTopAppBar
|
|||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.Scaffold
|
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.draw.scale
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
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.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
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.Font
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
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.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -199,7 +188,7 @@ fun TroubleshootingScreen(navController: NavController) {
|
|||||||
val buttonBgColor = if (isSystemInDarkTheme()) Color(0xFF333333) else Color(0xFFDDDDDD)
|
val buttonBgColor = if (isSystemInDarkTheme()) Color(0xFF333333) else Color(0xFFDDDDDD)
|
||||||
|
|
||||||
var instructionText by remember { mutableStateOf("") }
|
var instructionText by remember { mutableStateOf("") }
|
||||||
var isDarkTheme = isSystemInDarkTheme()
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
var mDensity by remember { mutableFloatStateOf(0f) }
|
var mDensity by remember { mutableFloatStateOf(0f) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|||||||
@@ -753,6 +753,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
fun cameraClosed() {
|
fun cameraClosed() {
|
||||||
cameraActive = false
|
cameraActive = false
|
||||||
setupStemActions()
|
setupStemActions()
|
||||||
@@ -894,7 +895,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
this@AirPodsService,
|
this@AirPodsService,
|
||||||
(batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0),
|
(batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0),
|
||||||
IslandType.MOVED_TO_OTHER_DEVICE,
|
IslandType.MOVED_TO_OTHER_DEVICE,
|
||||||
reversed = reasonReverseTapped
|
reversed = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!aacpManager.owns) {
|
if (!aacpManager.owns) {
|
||||||
@@ -909,12 +910,12 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onShowNearbyUI() {
|
override fun onShowNearbyUI() {
|
||||||
// showIsland(
|
showIsland(
|
||||||
// this@AirPodsService,
|
this@AirPodsService,
|
||||||
// (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0),
|
(batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0),
|
||||||
// IslandType.MOVED_TO_OTHER_DEVICE,
|
IslandType.MOVED_TO_OTHER_DEVICE,
|
||||||
// reversed = false
|
reversed = false
|
||||||
// )
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDeviceMetadataReceived(deviceMetadata: ByteArray) {
|
override fun onDeviceMetadataReceived(deviceMetadata: ByteArray) {
|
||||||
@@ -1462,7 +1463,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setBatteryMetadata() {
|
fun setBatteryMetadata() {
|
||||||
device?.let {
|
device?.let { it ->
|
||||||
SystemApisUtils.setMetadata(
|
SystemApisUtils.setMetadata(
|
||||||
it,
|
it,
|
||||||
it.METADATA_UNTETHERED_CASE_BATTERY,
|
it.METADATA_UNTETHERED_CASE_BATTERY,
|
||||||
@@ -1502,7 +1503,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
val componentName = ComponentName(this, BatteryWidget::class.java)
|
val componentName = ComponentName(this, BatteryWidget::class.java)
|
||||||
val widgetIds = appWidgetManager.getAppWidgetIds(componentName)
|
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)
|
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)
|
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) View.VISIBLE else View.GONE
|
||||||
)
|
)
|
||||||
if (widgetMobileBatteryEnabled) {
|
if (widgetMobileBatteryEnabled) {
|
||||||
val batteryManager = getSystemService<BatteryManager>(BatteryManager::class.java)
|
val batteryManager = getSystemService(BatteryManager::class.java)
|
||||||
val batteryLevel =
|
val batteryLevel =
|
||||||
batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
|
batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
|
||||||
val charging =
|
val charging =
|
||||||
@@ -1606,7 +1607,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
val appWidgetManager = AppWidgetManager.getInstance(this)
|
val appWidgetManager = AppWidgetManager.getInstance(this)
|
||||||
val componentName = ComponentName(this, NoiseControlWidget::class.java)
|
val componentName = ComponentName(this, NoiseControlWidget::class.java)
|
||||||
val widgetIds = appWidgetManager.getAppWidgetIds(componentName)
|
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 ancStatus = ancNotification.status
|
||||||
val allowOffModeValue = aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION }
|
val allowOffModeValue = aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION }
|
||||||
val allowOffMode = allowOffModeValue?.value?.takeIf { it.isNotEmpty() }?.get(0) == 0x01.toByte()
|
val allowOffMode = allowOffModeValue?.value?.takeIf { it.isNotEmpty() }?.get(0) == 0x01.toByte()
|
||||||
@@ -2198,7 +2199,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
Log.d("AirPodsService", macAddress)
|
Log.d("AirPodsService", macAddress)
|
||||||
|
|
||||||
sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", false) }
|
sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", false) }
|
||||||
device = getSystemService<BluetoothManager>(BluetoothManager::class.java).adapter.bondedDevices.find {
|
device = getSystemService(BluetoothManager::class.java).adapter.bondedDevices.find {
|
||||||
it.address == macAddress
|
it.address == macAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2335,7 +2336,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
setupStemActions()
|
setupStemActions()
|
||||||
|
|
||||||
while (socket.isConnected) {
|
while (socket.isConnected) {
|
||||||
socket.let {
|
socket.let { it ->
|
||||||
val buffer = ByteArray(1024)
|
val buffer = ByteArray(1024)
|
||||||
val bytesRead = it.inputStream.read(buffer)
|
val bytesRead = it.inputStream.read(buffer)
|
||||||
var data: ByteArray
|
var data: ByteArray
|
||||||
|
|||||||
@@ -423,7 +423,7 @@ class AACPManager {
|
|||||||
)
|
)
|
||||||
Log.d(
|
Log.d(
|
||||||
TAG, "Control command list is now: ${
|
TAG, "Control command list is now: ${
|
||||||
controlCommandStatusList.joinToString(", ") {
|
controlCommandStatusList.joinToString(", ") { it ->
|
||||||
"${it.identifier.name} (${it.identifier.value.toHexString()}) - ${
|
"${it.identifier.name} (${it.identifier.value.toHexString()}) - ${
|
||||||
it.value.joinToString(
|
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}"))) {
|
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")
|
throw IllegalArgumentException("MAC address must be 6 bytes")
|
||||||
}
|
}
|
||||||
Log.d(TAG, "SELFMAC: ${selfMacAddress}, TARGETMAC: ${targetMacAddress}")
|
Log.d(TAG, "SELFMAC: ${selfMacAddress}, TARGETMAC: $targetMacAddress")
|
||||||
Log.d(TAG, "Sending Media Information packet to ${targetMacAddress}")
|
Log.d(TAG, "Sending Media Information packet to $targetMacAddress")
|
||||||
return sendDataPacket(createMediaInformationNewDevicePacket(selfMacAddress, 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}"))) {
|
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")
|
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
|
val targetMac = connectedDevices.find { it.mac != selfMacAddress }?.mac
|
||||||
Log.d(TAG, "Sending Media Information packet to ${targetMac ?: "unknown device"}")
|
Log.d(TAG, "Sending Media Information packet to ${targetMac ?: "unknown device"}")
|
||||||
return sendDataPacket(
|
return sendDataPacket(
|
||||||
@@ -842,7 +842,7 @@ class AACPManager {
|
|||||||
|
|
||||||
fun createSmartRoutingShowUIPacket(targetMacAddress: String): ByteArray {
|
fun createSmartRoutingShowUIPacket(targetMacAddress: String): ByteArray {
|
||||||
val opcode = byteArrayOf(Opcodes.SMART_ROUTING, 0x00)
|
val opcode = byteArrayOf(Opcodes.SMART_ROUTING, 0x00)
|
||||||
val buffer = ByteBuffer.allocate(134)
|
val buffer = ByteBuffer.allocate(134)
|
||||||
buffer.put(
|
buffer.put(
|
||||||
targetMacAddress.split(":").map { it.toInt(16).toByte() }.toByteArray().reversedArray()
|
targetMacAddress.split(":").map { it.toInt(16).toByte() }.toByteArray().reversedArray()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import android.content.SharedPreferences
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import me.kavishdevar.librepods.services.ServiceManager
|
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
import kotlin.io.encoding.Base64
|
import kotlin.io.encoding.Base64
|
||||||
@@ -223,6 +222,7 @@ class BLEManager(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("GetInstance")
|
||||||
private fun decryptLastBytes(data: ByteArray, key: ByteArray): ByteArray? {
|
private fun decryptLastBytes(data: ByteArray, key: ByteArray): ByteArray? {
|
||||||
return try {
|
return try {
|
||||||
if (data.size < 16) {
|
if (data.size < 16) {
|
||||||
@@ -302,7 +302,7 @@ class BLEManager(private val context: Context) {
|
|||||||
|
|
||||||
if (previousGlobalState != parsedStatus.lidOpen) {
|
if (previousGlobalState != parsedStatus.lidOpen) {
|
||||||
listener.onLidStateChanged(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}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package me.kavishdevar.librepods.utils
|
package me.kavishdevar.librepods.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ object BluetoothCryptography {
|
|||||||
* @param data The data to encrypt
|
* @param data The data to encrypt
|
||||||
* @return The encrypted data
|
* @return The encrypted data
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("GetInstance")
|
||||||
fun e(key: ByteArray, data: ByteArray): ByteArray {
|
fun e(key: ByteArray, data: ByteArray): ByteArray {
|
||||||
val swappedKey = key.reversedArray()
|
val swappedKey = key.reversedArray()
|
||||||
val swappedData = data.reversedArray()
|
val swappedData = data.reversedArray()
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import android.content.Intent
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.ParcelUuid
|
import android.os.ParcelUuid
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.core.content.edit
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -76,7 +77,7 @@ object CrossDevice {
|
|||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
Log.d("CrossDevice", "Initializing CrossDevice")
|
Log.d("CrossDevice", "Initializing CrossDevice")
|
||||||
sharedPreferences = context.getSharedPreferences("packet_logs", Context.MODE_PRIVATE)
|
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.bluetoothAdapter = context.getSystemService(BluetoothManager::class.java).adapter
|
||||||
this@CrossDevice.bluetoothLeAdvertiser = bluetoothAdapter.bluetoothLeAdvertiser
|
this@CrossDevice.bluetoothLeAdvertiser = bluetoothAdapter.bluetoothLeAdvertiser
|
||||||
// startAdvertising()
|
// startAdvertising()
|
||||||
@@ -111,7 +112,7 @@ object CrossDevice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission", "unused")
|
||||||
private fun startAdvertising() {
|
private fun startAdvertising() {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val settings = AdvertiseSettings.Builder()
|
val settings = AdvertiseSettings.Builder()
|
||||||
@@ -147,7 +148,7 @@ object CrossDevice {
|
|||||||
fun setAirPodsConnected(connected: Boolean) {
|
fun setAirPodsConnected(connected: Boolean) {
|
||||||
if (connected) {
|
if (connected) {
|
||||||
isAvailable = false
|
isAvailable = false
|
||||||
sharedPreferences.edit().putBoolean("CrossDeviceIsAvailable", false).apply()
|
sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", false)}
|
||||||
clientSocket?.outputStream?.write(CrossDevicePackets.AIRPODS_CONNECTED.packet)
|
clientSocket?.outputStream?.write(CrossDevicePackets.AIRPODS_CONNECTED.packet)
|
||||||
} else {
|
} else {
|
||||||
clientSocket?.outputStream?.write(CrossDevicePackets.AIRPODS_DISCONNECTED.packet)
|
clientSocket?.outputStream?.write(CrossDevicePackets.AIRPODS_DISCONNECTED.packet)
|
||||||
@@ -168,7 +169,7 @@ object CrossDevice {
|
|||||||
val logEntry = "$source: $packetHex"
|
val logEntry = "$source: $packetHex"
|
||||||
val logs = sharedPreferences.getStringSet(PACKET_LOG_KEY, mutableSetOf())?.toMutableSet() ?: mutableSetOf()
|
val logs = sharedPreferences.getStringSet(PACKET_LOG_KEY, mutableSetOf())?.toMutableSet() ?: mutableSetOf()
|
||||||
logs.add(logEntry)
|
logs.add(logEntry)
|
||||||
sharedPreferences.edit().putStringSet(PACKET_LOG_KEY, logs).apply()
|
sharedPreferences.edit { putStringSet(PACKET_LOG_KEY, logs)}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
@@ -207,10 +208,10 @@ object CrossDevice {
|
|||||||
}
|
}
|
||||||
} else if (packet.contentEquals(CrossDevicePackets.AIRPODS_CONNECTED.packet)) {
|
} else if (packet.contentEquals(CrossDevicePackets.AIRPODS_CONNECTED.packet)) {
|
||||||
isAvailable = true
|
isAvailable = true
|
||||||
sharedPreferences.edit().putBoolean("CrossDeviceIsAvailable", true).apply()
|
sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", true)}
|
||||||
} else if (packet.contentEquals(CrossDevicePackets.AIRPODS_DISCONNECTED.packet)) {
|
} else if (packet.contentEquals(CrossDevicePackets.AIRPODS_DISCONNECTED.packet)) {
|
||||||
isAvailable = false
|
isAvailable = false
|
||||||
sharedPreferences.edit().putBoolean("CrossDeviceIsAvailable", false).apply()
|
sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", false)}
|
||||||
} else if (packet.contentEquals(CrossDevicePackets.REQUEST_BATTERY_BYTES.packet)) {
|
} else if (packet.contentEquals(CrossDevicePackets.REQUEST_BATTERY_BYTES.packet)) {
|
||||||
Log.d("CrossDevice", "Received battery request, battery data: ${batteryBytes.joinToString("") { "%02x".format(it) }}")
|
Log.d("CrossDevice", "Received battery request, battery data: ${batteryBytes.joinToString("") { "%02x".format(it) }}")
|
||||||
sendRemotePacket(batteryBytes)
|
sendRemotePacket(batteryBytes)
|
||||||
@@ -223,7 +224,7 @@ object CrossDevice {
|
|||||||
} else {
|
} else {
|
||||||
if (packet.sliceArray(0..3).contentEquals(CrossDevicePackets.AIRPODS_DATA_HEADER.packet)) {
|
if (packet.sliceArray(0..3).contentEquals(CrossDevicePackets.AIRPODS_DATA_HEADER.packet)) {
|
||||||
isAvailable = true
|
isAvailable = true
|
||||||
sharedPreferences.edit().putBoolean("CrossDeviceIsAvailable", true).apply()
|
sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", true) }
|
||||||
if (packet.size % 2 == 0) {
|
if (packet.size % 2 == 0) {
|
||||||
val half = packet.size / 2
|
val half = packet.size / 2
|
||||||
if (packet.sliceArray(0 until half).contentEquals(packet.sliceArray(half until packet.size))) {
|
if (packet.sliceArray(0 until half).contentEquals(packet.sliceArray(half until packet.size))) {
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class IslandWindow(private val context: Context) {
|
|||||||
intent.getParcelableArrayListExtra("data", Battery::class.java)
|
intent.getParcelableArrayListExtra("data", Battery::class.java)
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
intent.getParcelableArrayListExtra<Battery>("data")
|
intent.getParcelableArrayListExtra("data")
|
||||||
}
|
}
|
||||||
updateBatteryDisplay(batteryList)
|
updateBatteryDisplay(batteryList)
|
||||||
} else if (intent?.action == AirPodsNotifications.DISCONNECT_RECEIVERS) {
|
} else if (intent?.action == AirPodsNotifications.DISCONNECT_RECEIVERS) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package me.kavishdevar.librepods.utils
|
package me.kavishdevar.librepods.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
@@ -17,6 +18,7 @@ import android.widget.FrameLayout
|
|||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import androidx.core.net.toUri
|
||||||
import io.github.libxposed.api.XposedInterface
|
import io.github.libxposed.api.XposedInterface
|
||||||
import io.github.libxposed.api.XposedInterface.AfterHookCallback
|
import io.github.libxposed.api.XposedInterface.AfterHookCallback
|
||||||
import io.github.libxposed.api.XposedModule
|
import io.github.libxposed.api.XposedModule
|
||||||
@@ -27,7 +29,7 @@ import io.github.libxposed.api.annotations.XposedHooker
|
|||||||
|
|
||||||
private const val TAG = "AirPodsHook"
|
private const val TAG = "AirPodsHook"
|
||||||
private lateinit var module: KotlinModule
|
private lateinit var module: KotlinModule
|
||||||
|
@SuppressLint("DiscouragedApi", "PrivateApi")
|
||||||
class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModule(base, param) {
|
class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModule(base, param) {
|
||||||
init {
|
init {
|
||||||
Log.i(TAG, "AirPodsHook module initialized at :: ${param.processName}")
|
Log.i(TAG, "AirPodsHook module initialized at :: ${param.processName}")
|
||||||
@@ -60,7 +62,7 @@ class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModul
|
|||||||
|
|
||||||
val updateIconMethod = headerControllerClass.getDeclaredMethod(
|
val updateIconMethod = headerControllerClass.getDeclaredMethod(
|
||||||
"updateIcon",
|
"updateIcon",
|
||||||
android.widget.ImageView::class.java,
|
ImageView::class.java,
|
||||||
String::class.java)
|
String::class.java)
|
||||||
|
|
||||||
hook(updateIconMethod, BluetoothIconHooker::class.java)
|
hook(updateIconMethod, BluetoothIconHooker::class.java)
|
||||||
@@ -89,7 +91,7 @@ class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModul
|
|||||||
|
|
||||||
val updateIconMethod = headerControllerClass.getDeclaredMethod(
|
val updateIconMethod = headerControllerClass.getDeclaredMethod(
|
||||||
"updateIcon",
|
"updateIcon",
|
||||||
android.widget.ImageView::class.java,
|
ImageView::class.java,
|
||||||
String::class.java)
|
String::class.java)
|
||||||
|
|
||||||
hook(updateIconMethod, BluetoothIconHooker::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 imageView = callback.args[0] as ImageView
|
||||||
val iconUri = callback.args[1] as String
|
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")) {
|
if (uri.toString().startsWith("android.resource://me.kavishdevar.librepods")) {
|
||||||
Log.i(TAG, "Handling AirPods icon URI: $uri")
|
Log.i(TAG, "Handling AirPods icon URI: $uri")
|
||||||
|
|
||||||
@@ -571,10 +573,10 @@ class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModul
|
|||||||
|
|
||||||
addView(icon)
|
addView(icon)
|
||||||
|
|
||||||
if (isSelected) {
|
background = if (isSelected) {
|
||||||
background = createSelectedBackground(context)
|
createSelectedBackground(context)
|
||||||
} else {
|
} else {
|
||||||
background = null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
|
|||||||
@@ -19,8 +19,6 @@
|
|||||||
package me.kavishdevar.librepods.utils
|
package me.kavishdevar.librepods.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ object MediaController {
|
|||||||
} else {
|
} else {
|
||||||
initialVolume!!
|
initialVolume!!
|
||||||
}
|
}
|
||||||
smoothVolumeTransition(initialVolume!!, targetVolume.toInt())
|
smoothVolumeTransition(initialVolume!!, targetVolume)
|
||||||
if (conversationalAwarenessPauseMusic) {
|
if (conversationalAwarenessPauseMusic) {
|
||||||
sendPause(force = true)
|
sendPause(force = true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ import me.kavishdevar.librepods.constants.AirPodsNotifications
|
|||||||
import me.kavishdevar.librepods.constants.Battery
|
import me.kavishdevar.librepods.constants.Battery
|
||||||
import me.kavishdevar.librepods.constants.BatteryComponent
|
import me.kavishdevar.librepods.constants.BatteryComponent
|
||||||
import me.kavishdevar.librepods.constants.BatteryStatus
|
import me.kavishdevar.librepods.constants.BatteryStatus
|
||||||
import kotlin.collections.find
|
|
||||||
|
|
||||||
@SuppressLint("InflateParams", "ClickableViewAccessibility")
|
@SuppressLint("InflateParams", "ClickableViewAccessibility")
|
||||||
class PopupWindow(
|
class PopupWindow(
|
||||||
@@ -172,7 +171,12 @@ class PopupWindow(
|
|||||||
batteryUpdateReceiver = object : BroadcastReceiver() {
|
batteryUpdateReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
if (intent?.action == AirPodsNotifications.BATTERY_DATA) {
|
if (intent?.action == AirPodsNotifications.BATTERY_DATA) {
|
||||||
val batteryList = intent.getParcelableArrayListExtra<Battery>("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) {
|
if (batteryList != null) {
|
||||||
updateBatteryStatusFromList(batteryList)
|
updateBatteryStatusFromList(batteryList)
|
||||||
}
|
}
|
||||||
@@ -272,7 +276,4 @@ class PopupWindow(
|
|||||||
onCloseCallback()
|
onCloseCallback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isShowing: Boolean
|
|
||||||
get() = mView.parent != null && !isClosing
|
|
||||||
}
|
}
|
||||||
|
|||||||
10
android/app/src/main/res/drawable/app_widget_background.xml
Normal file
10
android/app/src/main/res/drawable/app_widget_background.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Background for widgets to make the rounded corners based on the
|
||||||
|
appWidgetRadius attribute value
|
||||||
|
-->
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<corners android:radius="?attr/appWidgetRadius" />
|
||||||
|
<solid android:color="?android:attr/colorBackground" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Background for views inside widgets to make the rounded corners based on the
|
||||||
|
appWidgetInnerRadius attribute value
|
||||||
|
-->
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="?attr/appWidgetInnerRadius" />
|
||||||
|
<solid android:color="?android:attr/colorAccent" />
|
||||||
|
</shape>
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:translationX="-12dp"
|
android:translationX="-12dp"
|
||||||
android:background="@drawable/ic_undo_button_bg"
|
android:background="@drawable/ic_undo_button_bg"
|
||||||
android:contentDescription="Undo button"
|
android:contentDescription="@string/undo"
|
||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
android:src="@drawable/ic_undo"
|
android:src="@drawable/ic_undo"
|
||||||
android:tint="@android:color/white"
|
android:tint="@android:color/white"
|
||||||
|
|||||||
@@ -4,12 +4,14 @@
|
|||||||
android:id="@+id/noise_control_widget"
|
android:id="@+id/noise_control_widget"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:theme="@style/Theme.LibrePods.AppWidgetContainer">
|
android:theme="@style/Theme.LibrePods.AppWidgetContainer"
|
||||||
|
tools:ignore="ContentDescription,NestedWeights">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@android:id/background"
|
android:id="@android:id/background"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:baselineAligned="false"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -70,7 +72,8 @@
|
|||||||
android:shadowRadius="12"
|
android:shadowRadius="12"
|
||||||
android:text="@string/transparency"
|
android:text="@string/transparency"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="12sp" />
|
android:textSize="12sp"
|
||||||
|
tools:ignore="NestedWeights" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -102,7 +105,8 @@
|
|||||||
android:shadowRadius="12"
|
android:shadowRadius="12"
|
||||||
android:text="@string/adaptive"
|
android:text="@string/adaptive"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="12sp" />
|
android:textSize="12sp"
|
||||||
|
tools:ignore="NestedWeights" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
6
android/app/src/main/res/mipmap-anydpi/ic_launcher.xml
Normal file
6
android/app/src/main/res/mipmap-anydpi/ic_launcher.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_monochrome" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||||
|
</adaptive-icon>
|
||||||
Binary file not shown.
@@ -1,14 +0,0 @@
|
|||||||
<resources>
|
|
||||||
|
|
||||||
<style name="Widget.LibrePods.AppWidget.Container" parent="android:Widget">
|
|
||||||
<item name="android:id">@android:id/background</item>
|
|
||||||
<item name="android:padding">?attr/appWidgetPadding</item>
|
|
||||||
<item name="android:background">@drawable/app_widget_background</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="Widget.LibrePods.AppWidget.InnerView" parent="android:Widget">
|
|
||||||
<item name="android:padding">?attr/appWidgetPadding</item>
|
|
||||||
<item name="android:background">@drawable/app_widget_inner_view_background</item>
|
|
||||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
||||||
@@ -81,4 +81,5 @@
|
|||||||
<string name="takeover_ringing_call_desc">Your phone starts ringing</string>
|
<string name="takeover_ringing_call_desc">Your phone starts ringing</string>
|
||||||
<string name="takeover_media_start">Starting media playback</string>
|
<string name="takeover_media_start">Starting media playback</string>
|
||||||
<string name="takeover_media_start_desc">Your phone starts playing media</string>
|
<string name="takeover_media_start_desc">Your phone starts playing media</string>
|
||||||
|
<string name="undo">Undo</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Widget.LibrePods.AppWidget.Container" parent="android:Widget">
|
<style name="Widget.LibrePods.AppWidget.Container" parent="android:Widget"></style>
|
||||||
<item name="android:id">@android:id/background</item>
|
|
||||||
<item name="android:background">?android:attr/colorBackground</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="Widget.LibrePods.AppWidget.InnerView" parent="android:Widget">
|
<style name="Widget.LibrePods.AppWidget.InnerView" parent="android:Widget"></style>
|
||||||
<item name="android:background">?android:attr/colorBackground</item>
|
|
||||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user