split compose functions, and organize stuff

This commit is contained in:
Kavish Devar
2024-12-13 01:41:37 +05:30
parent 7b03a7e9f0
commit 809818f60d
32 changed files with 1733 additions and 1273 deletions

View File

@@ -67,7 +67,7 @@
android:foregroundServiceType="connectedDevice" android:foregroundServiceType="connectedDevice"
android:permission="android.permission.BLUETOOTH_CONNECT" /> android:permission="android.permission.BLUETOOTH_CONNECT" />
<service <service
android:name=".AirPodsQSService" android:name=".services.AirPodsQSService"
android:exported="true" android:exported="true"
android:icon="@drawable/airpods" android:icon="@drawable/airpods"
android:label="ANC Mode" android:label="ANC Mode"
@@ -78,7 +78,7 @@
</service> </service>
<receiver <receiver
android:name=".BootReceiver" android:name=".receivers.BootReceiver"
android:enabled="true" android:enabled="true"
android:exported="true" android:exported="true"
android:label="@string/app_name"> android:label="@string/app_name">

View File

@@ -23,7 +23,9 @@ import android.widget.RemoteViews
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.kavishdevar.aln.utils.MediaController
import org.lsposed.hiddenapibypass.HiddenApiBypass import org.lsposed.hiddenapibypass.HiddenApiBypass
object ServiceManager { object ServiceManager {
@@ -36,11 +38,11 @@ object ServiceManager {
fun setService(service: AirPodsService?) { fun setService(service: AirPodsService?) {
this.service = service this.service = service
} }
@Synchronized // @Synchronized
fun restartService(context: Context) { // fun restartService(context: Context) {
service?.stopSelf() // service?.stopSelf()
context.startService(Intent(context, AirPodsService::class.java)) // context.startService(Intent(context, AirPodsService::class.java))
} // }
} }
@Suppress("unused") @Suppress("unused")
@@ -76,6 +78,10 @@ class AirPodsService: Service() {
if (bluetoothDevice != null && action != null && !action.isEmpty()) { if (bluetoothDevice != null && action != null && !action.isEmpty()) {
Log.d("AirPodsService", "Received bluetooth connection broadcast") Log.d("AirPodsService", "Received bluetooth connection broadcast")
if (BluetoothDevice.ACTION_ACL_CONNECTED == action) { if (BluetoothDevice.ACTION_ACL_CONNECTED == action) {
if (ServiceManager.getService()?.isConnected == true) {
ServiceManager.getService()?.manuallyCheckForAudioSource()
return
}
val uuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a") val uuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
if (bluetoothDevice.uuids.contains(uuid)) { if (bluetoothDevice.uuids.contains(uuid)) {
val intent = Intent(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED) val intent = Intent(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED)
@@ -84,8 +90,6 @@ class AirPodsService: Service() {
context?.sendBroadcast(intent) context?.sendBroadcast(intent)
} }
} }
// Airpods disconnected, remove notification but leave the scanner going.
if (BluetoothDevice.ACTION_ACL_DISCONNECTED == action if (BluetoothDevice.ACTION_ACL_DISCONNECTED == action
|| BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED == action || BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED == action
) { ) {
@@ -203,7 +207,19 @@ class AirPodsService: Service() {
Log.d("AirPodsService", "Service started") Log.d("AirPodsService", "Service started")
ServiceManager.setService(this) ServiceManager.setService(this)
startForegroundNotification() startForegroundNotification()
registerReceiver(bluetoothReceiver, BluetoothReceiver.buildFilter(), RECEIVER_EXPORTED) val serviceIntentFilter = IntentFilter().apply {
addAction("android.bluetooth.device.action.ACL_CONNECTED")
addAction("android.bluetooth.device.action.ACL_DISCONNECTED")
addAction("android.bluetooth.device.action.BOND_STATE_CHANGED")
addAction("android.bluetooth.device.action.NAME_CHANGED")
addAction("android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED")
addAction("android.bluetooth.adapter.action.STATE_CHANGED")
addAction("android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED")
addAction("android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT")
addAction("android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED")
addAction("android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED")
}
registerReceiver(bluetoothReceiver, serviceIntentFilter, RECEIVER_EXPORTED)
connectionReceiver = object: BroadcastReceiver() { connectionReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
@@ -225,11 +241,11 @@ class AirPodsService: Service() {
} }
} }
val intentFilter = IntentFilter().apply { val deviceIntentFilter = IntentFilter().apply {
addAction(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED) addAction(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED)
addAction(AirPodsNotifications.AIRPODS_DISCONNECTED) addAction(AirPodsNotifications.AIRPODS_DISCONNECTED)
} }
registerReceiver(connectionReceiver, intentFilter, RECEIVER_EXPORTED) registerReceiver(connectionReceiver, deviceIntentFilter, RECEIVER_EXPORTED)
val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter
bluetoothAdapter.bondedDevices.forEach { device -> bluetoothAdapter.bondedDevices.forEach { device ->
@@ -258,6 +274,13 @@ class AirPodsService: Service() {
private lateinit var socket: BluetoothSocket private lateinit var socket: BluetoothSocket
fun manuallyCheckForAudioSource() {
if (earDetectionNotification.status[0] != 0.toByte() && earDetectionNotification.status[1] != 0.toByte()) {
Log.d("AirPodsService", "For some reason, Android connected to the audio profile itself even after disconnecting. Disconnecting audio profile again!")
disconnectAudio(this, device)
}
}
@SuppressLint("MissingPermission", "UnspecifiedRegisterReceiverFlag") @SuppressLint("MissingPermission", "UnspecifiedRegisterReceiverFlag")
fun connectToSocket(device: BluetoothDevice) { fun connectToSocket(device: BluetoothDevice) {
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;") HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
@@ -303,18 +326,32 @@ class AirPodsService: Service() {
this@AirPodsService.device = device this@AirPodsService.device = device
isConnected = true isConnected = true
socket.let { it -> socket.let { it ->
// sometimes doesn't work ;-;
// i though i move it to the coroutine
// but, the socket sometimes disconnects if i don't send a packet outside of the routine first
// so, sending *again*, with a delay, in the coroutine
it.outputStream.write(Enums.HANDSHAKE.value) it.outputStream.write(Enums.HANDSHAKE.value)
it.outputStream.flush() it.outputStream.flush()
it.outputStream.write(Enums.SET_SPECIFIC_FEATURES.value) it.outputStream.write(Enums.SET_SPECIFIC_FEATURES.value)
it.outputStream.flush() it.outputStream.flush()
it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value) it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value)
it.outputStream.flush() it.outputStream.flush()
CoroutineScope(Dispatchers.IO).launch {
// this is so stupid, why does it disconnect if i don't send a packet outside of the coroutine first
it.outputStream.write(Enums.HANDSHAKE.value)
it.outputStream.flush()
delay(200)
it.outputStream.write(Enums.SET_SPECIFIC_FEATURES.value)
it.outputStream.flush()
delay(200)
it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value)
it.outputStream.flush()
delay(200)
sendBroadcast( sendBroadcast(
Intent(AirPodsNotifications.AIRPODS_CONNECTED) Intent(AirPodsNotifications.AIRPODS_CONNECTED)
.putExtra("device", device) .putExtra("device", device)
) )
CoroutineScope(Dispatchers.IO).launch {
while (socket.isConnected == true) { while (socket.isConnected == true) {
socket.let { socket.let {
val audioManager = val audioManager =
@@ -516,6 +553,7 @@ class AirPodsService: Service() {
} }
fun setANCMode(mode: Int) { fun setANCMode(mode: Int) {
Log.d("AirPodsService", "setANCMode: $mode")
when (mode) { when (mode) {
1 -> { 1 -> {
socket.outputStream?.write(Enums.NOISE_CANCELLATION_OFF.value) socket.outputStream?.write(Enums.NOISE_CANCELLATION_OFF.value)

View File

@@ -1,14 +0,0 @@
package me.kavishdevar.aln
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
class BootReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
Intent.ACTION_MY_PACKAGE_REPLACED -> try { context?.startForegroundService(Intent(context, AirPodsService::class.java)) } catch (e: Exception) { e.printStackTrace() }
Intent.ACTION_BOOT_COMPLETED -> try { context?.startForegroundService(Intent(context, AirPodsService::class.java)) } catch (e: Exception) { e.printStackTrace() }
}
}
}

View File

@@ -34,6 +34,9 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState import com.google.accompanist.permissions.rememberMultiplePermissionsState
import me.kavishdevar.aln.screens.AirPodsSettingsScreen
import me.kavishdevar.aln.screens.DebugScreen
import me.kavishdevar.aln.screens.LongPress
import me.kavishdevar.aln.ui.theme.ALNTheme import me.kavishdevar.aln.ui.theme.ALNTheme
lateinit var serviceConnection: ServiceConnection lateinit var serviceConnection: ServiceConnection
@@ -66,6 +69,7 @@ class MainActivity : ComponentActivity() {
} catch (e: Exception) { } catch (e: Exception) {
Log.e("MainActivity", "Error while unregistering receiver: $e") Log.e("MainActivity", "Error while unregistering receiver: $e")
} }
sendBroadcast(Intent(AirPodsNotifications.DISCONNECT_RECEIVERS))
super.onDestroy() super.onDestroy()
} }
@@ -147,7 +151,10 @@ fun Main() {
DebugScreen(navController = navController) DebugScreen(navController = navController)
} }
composable("long_press/{bud}") { navBackStackEntry -> composable("long_press/{bud}") { navBackStackEntry ->
LongPress(navController = navController, name = navBackStackEntry.arguments?.getString("bud")!!) LongPress(
navController = navController,
name = navBackStackEntry.arguments?.getString("bud")!!
)
} }
} }

View File

@@ -75,6 +75,7 @@ class AirPodsNotifications {
const val CA_DATA = "me.kavishdevar.aln.CA_DATA" const val CA_DATA = "me.kavishdevar.aln.CA_DATA"
const val AIRPODS_DISCONNECTED = "me.kavishdevar.aln.AIRPODS_DISCONNECTED" const val AIRPODS_DISCONNECTED = "me.kavishdevar.aln.AIRPODS_DISCONNECTED"
const val AIRPODS_CONNECTION_DETECTED = "me.kavishdevar.aln.AIRPODS_CONNECTION_DETECTED" const val AIRPODS_CONNECTION_DETECTED = "me.kavishdevar.aln.AIRPODS_CONNECTION_DETECTED"
const val DISCONNECT_RECEIVERS = "me.kavishdevar.aln.DISCONNECT_RECEIVERS"
} }
class EarDetection { class EarDetection {

View File

@@ -0,0 +1,80 @@
package me.kavishdevar.aln.composables
import android.content.Context
import android.content.SharedPreferences
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.kavishdevar.aln.AirPodsService
@Composable
fun AccessibilitySettings(service: AirPodsService, sharedPreferences: SharedPreferences) {
val isDarkTheme = isSystemInDarkTheme()
val textColor = if (isDarkTheme) Color.White else Color.Black
Text(
text = "ACCESSIBILITY",
style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Light,
color = textColor.copy(alpha = 0.6f)
),
modifier = Modifier.padding(8.dp, bottom = 2.dp)
)
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
Column(
modifier = Modifier
.fillMaxWidth()
.background(backgroundColor, RoundedCornerShape(14.dp))
.padding(top = 2.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
) {
Text(
text = "Tone Volume",
modifier = Modifier
.padding(end = 8.dp, bottom = 2.dp, start = 2.dp)
.fillMaxWidth(),
style = TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = textColor
)
)
ToneVolumeSlider(service = service, sharedPreferences = sharedPreferences)
}
// TODO: Dropdown menu with 3 options, Default, Slower, Slowest Press speed
// TODO: Dropdown menu with 3 options, Default, Slower, Slowest Press and hold duration
// TODO: Dropdown menu with 3 options, Default, Slower, Slowest Volume Swipe Speed
SinglePodANCSwitch(service = service, sharedPreferences = sharedPreferences)
VolumeControlSwitch(service = service, sharedPreferences = sharedPreferences)
}
}
@Preview
@Composable
fun AccessibilitySettingsPreview() {
AccessibilitySettings(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", Context.MODE_PRIVATE))
}

View File

@@ -0,0 +1,137 @@
package me.kavishdevar.aln.composables
import android.content.Context
import android.content.SharedPreferences
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.kavishdevar.aln.AirPodsService
import kotlin.math.roundToInt
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AdaptiveStrengthSlider(service: AirPodsService, sharedPreferences: SharedPreferences) {
val sliderValue = remember { mutableFloatStateOf(0f) }
LaunchedEffect(sliderValue) {
if (sharedPreferences.contains("adaptive_strength")) {
sliderValue.floatValue = sharedPreferences.getInt("adaptive_strength", 0).toFloat()
}
}
LaunchedEffect(sliderValue.floatValue) {
sharedPreferences.edit().putInt("adaptive_strength", sliderValue.floatValue.toInt()).apply()
}
val isDarkTheme = isSystemInDarkTheme()
val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFFD9D9D9)
val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF)
val labelTextColor = if (isDarkTheme) Color.White else Color.Black
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Slider(
value = sliderValue.floatValue,
onValueChange = {
sliderValue.floatValue = it
service.setAdaptiveStrength(100 - it.toInt())
},
valueRange = 0f..100f,
onValueChangeFinished = {
sliderValue.floatValue = sliderValue.floatValue.roundToInt().toFloat()
},
modifier = Modifier
.fillMaxWidth()
.height(36.dp),
colors = SliderDefaults.colors(
thumbColor = thumbColor,
inactiveTrackColor = trackColor
),
thumb = {
Box(
modifier = Modifier
.size(24.dp)
.shadow(4.dp, CircleShape)
.background(thumbColor, CircleShape)
)
},
track = {
Box(
modifier = Modifier
.fillMaxWidth()
.height(12.dp),
contentAlignment = Alignment.CenterStart
)
{
Box(
modifier = Modifier
.fillMaxWidth()
.height(4.dp)
.background(trackColor, RoundedCornerShape(4.dp))
)
}
}
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Less Noise",
style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Light,
color = labelTextColor
),
modifier = Modifier.padding(start = 4.dp)
)
Text(
text = "More Noise",
style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Light,
color = labelTextColor
),
modifier = Modifier.padding(end = 4.dp)
)
}
}
}
@Preview
@Composable
fun AdaptiveStrengthSliderPreview() {
AdaptiveStrengthSlider(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", Context.MODE_PRIVATE))
}

View File

@@ -0,0 +1,87 @@
package me.kavishdevar.aln.composables
import android.content.Context
import android.content.SharedPreferences
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.kavishdevar.aln.AirPodsService
@Composable
fun AudioSettings(service: AirPodsService, sharedPreferences: SharedPreferences) {
val isDarkTheme = isSystemInDarkTheme()
val textColor = if (isDarkTheme) Color.White else Color.Black
Text(
text = "AUDIO",
style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Light,
color = textColor.copy(alpha = 0.6f)
),
modifier = Modifier.padding(8.dp, bottom = 2.dp)
)
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
Column(
modifier = Modifier
.fillMaxWidth()
.background(backgroundColor, RoundedCornerShape(14.dp))
.padding(top = 2.dp)
) {
PersonalizedVolumeSwitch(service = service, sharedPreferences = sharedPreferences)
ConversationalAwarenessSwitch(service = service, sharedPreferences = sharedPreferences)
LoudSoundReductionSwitch(service = service, sharedPreferences = sharedPreferences)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 10.dp)
) {
Text(
text = "Adaptive Audio",
modifier = Modifier
.padding(end = 8.dp, bottom = 2.dp, start = 2.dp)
.fillMaxWidth(),
style = TextStyle(
fontSize = 16.sp,
color = textColor
)
)
Text(
text = "Adaptive audio dynamically responds to your environment and cancels or allows external noise. You can customize Adaptive Audio to allow more or less noise.",
modifier = Modifier
.padding(bottom = 8.dp, top = 2.dp)
.padding(end = 2.dp, start = 2.dp)
.fillMaxWidth(),
style = TextStyle(
fontSize = 12.sp,
color = textColor.copy(alpha = 0.6f)
)
)
AdaptiveStrengthSlider(service = service, sharedPreferences = sharedPreferences)
}
}
}
@Preview
@Composable
fun AudioSettingsPreview() {
AudioSettings(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", Context.MODE_PRIVATE))
}

View File

@@ -22,6 +22,7 @@ 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.tooling.preview.Preview
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 me.kavishdevar.aln.R import me.kavishdevar.aln.R
@@ -106,3 +107,9 @@ fun BatteryIndicator(batteryPercentage: Int, charging: Boolean = false) {
) )
} }
} }
@Preview
@Composable
fun BatteryIndicatorPreview() {
BatteryIndicator(batteryPercentage = 48, charging = true)
}

View File

@@ -22,12 +22,12 @@ import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.imageResource import androidx.compose.ui.res.imageResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import me.kavishdevar.aln.AirPodsNotifications import me.kavishdevar.aln.AirPodsNotifications
import me.kavishdevar.aln.AirPodsService import me.kavishdevar.aln.AirPodsService
import me.kavishdevar.aln.Battery import me.kavishdevar.aln.Battery
import me.kavishdevar.aln.BatteryComponent import me.kavishdevar.aln.BatteryComponent
import me.kavishdevar.aln.composables.BatteryIndicator
import me.kavishdevar.aln.BatteryStatus import me.kavishdevar.aln.BatteryStatus
import me.kavishdevar.aln.R import me.kavishdevar.aln.R
@@ -37,6 +37,7 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) {
@Suppress("DEPRECATION") val batteryReceiver = remember { @Suppress("DEPRECATION") val batteryReceiver = remember {
object : BroadcastReceiver() { object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
if (intent.action == AirPodsNotifications.BATTERY_DATA) {
batteryStatus.value = batteryStatus.value =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableArrayListExtra("data", Battery::class.java) intent.getParcelableArrayListExtra("data", Battery::class.java)
@@ -44,12 +45,20 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) {
intent.getParcelableArrayListExtra("data") intent.getParcelableArrayListExtra("data")
}?.toList() ?: listOf() }?.toList() ?: listOf()
} }
else if (intent.action == AirPodsNotifications.DISCONNECT_RECEIVERS) {
context.unregisterReceiver(this)
}
}
} }
} }
val context = LocalContext.current val context = LocalContext.current
LaunchedEffect(context) { LaunchedEffect(context) {
val batteryIntentFilter = IntentFilter(AirPodsNotifications.BATTERY_DATA) val batteryIntentFilter = IntentFilter()
.apply {
addAction(AirPodsNotifications.BATTERY_DATA)
addAction(AirPodsNotifications.DISCONNECT_RECEIVERS)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver( context.registerReceiver(
batteryReceiver, batteryReceiver,
@@ -133,3 +142,9 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) {
} }
} }
} }
@Preview
@Composable
fun BatteryViewPreview() {
BatteryView(AirPodsService(), preview = true)
}

View File

@@ -0,0 +1,107 @@
package me.kavishdevar.aln.composables
import android.content.SharedPreferences
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.kavishdevar.aln.AirPodsService
@Composable
fun ConversationalAwarenessSwitch(service: AirPodsService, sharedPreferences: SharedPreferences) {
var conversationalAwarenessEnabled by remember {
mutableStateOf(
sharedPreferences.getBoolean("conversational_awareness", true)
)
}
fun updateConversationalAwareness(enabled: Boolean) {
conversationalAwarenessEnabled = enabled
sharedPreferences.edit().putBoolean("conversational_awareness", enabled).apply()
service.setCAEnabled(enabled)
}
val isDarkTheme = isSystemInDarkTheme()
val textColor = if (isDarkTheme) Color.White else Color.Black
val isPressed = remember { mutableStateOf(false) }
Row(
modifier = Modifier
.fillMaxWidth()
.background(
shape = RoundedCornerShape(14.dp),
color = if (isPressed.value) Color(0xFFE0E0E0) else Color.Transparent
)
.padding(horizontal = 12.dp, vertical = 12.dp)
.pointerInput(Unit) {
detectTapGestures(
onPress = {
isPressed.value = true
tryAwaitRelease()
isPressed.value = false
}
)
}
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
updateConversationalAwareness(!conversationalAwarenessEnabled)
},
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.weight(1f)
.padding(end = 4.dp)
) {
Text(
text = "Conversational Awareness",
fontSize = 16.sp,
color = textColor
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Lowers media volume and reduces background noise when you start speaking to other people.",
fontSize = 12.sp,
color = textColor.copy(0.6f),
lineHeight = 14.sp,
)
}
StyledSwitch(
checked = conversationalAwarenessEnabled,
onCheckedChange = {
updateConversationalAwareness(it)
},
)
}
}
@Preview
@Composable
fun ConversationalAwarenessSwitchPreview() {
ConversationalAwarenessSwitch(AirPodsService(), LocalContext.current.getSharedPreferences("preview", 0))
}

View File

@@ -0,0 +1,84 @@
package me.kavishdevar.aln.composables
import android.content.SharedPreferences
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.kavishdevar.aln.AirPodsService
@Composable
fun IndependentToggle(name: String, service: AirPodsService, functionName: String, sharedPreferences: SharedPreferences, default: Boolean = false) {
val isDarkTheme = isSystemInDarkTheme()
val textColor = if (isDarkTheme) Color.White else Color.Black
val snakeCasedName = name.replace(Regex("[\\W\\s]+"), "_").lowercase()
var checked by remember { mutableStateOf(default) }
LaunchedEffect(sharedPreferences) {
checked = sharedPreferences.getBoolean(snakeCasedName, true)
}
Box (
modifier = Modifier
.padding(vertical = 8.dp)
.background(
if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF),
RoundedCornerShape(14.dp)
)
.clickable {
checked = !checked
sharedPreferences
.edit()
.putBoolean(snakeCasedName, checked)
.apply()
val method = service::class.java.getMethod(functionName, Boolean::class.java)
method.invoke(service, checked)
},
)
{
Row(
modifier = Modifier
.fillMaxWidth()
.height(55.dp)
.padding(horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = name, modifier = Modifier.weight(1f), fontSize = 16.sp, color = textColor)
StyledSwitch(
checked = checked,
onCheckedChange = {
checked = it
sharedPreferences.edit().putBoolean(snakeCasedName, it).apply()
val method = service::class.java.getMethod(functionName, Boolean::class.java)
method.invoke(service, it)
},
)
}
}
}
@Preview
@Composable
fun IndependentTogglePreview() {
IndependentToggle("Test", AirPodsService(), "test", LocalContext.current.getSharedPreferences("preview", 0), true)
}

View File

@@ -0,0 +1,108 @@
package me.kavishdevar.aln.composables
import android.content.SharedPreferences
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.kavishdevar.aln.AirPodsService
@Composable
fun LoudSoundReductionSwitch(service: AirPodsService, sharedPreferences: SharedPreferences) {
var loudSoundReductionEnabled by remember {
mutableStateOf(
sharedPreferences.getBoolean("loud_sound_reduction", true)
)
}
fun updateLoudSoundReduction(enabled: Boolean) {
loudSoundReductionEnabled = enabled
sharedPreferences.edit().putBoolean("loud_sound_reduction", enabled).apply()
service.setLoudSoundReduction(enabled)
}
val isDarkTheme = isSystemInDarkTheme()
val textColor = if (isDarkTheme) Color.White else Color.Black
val isPressed = remember { mutableStateOf(false) }
Row(
modifier = Modifier
.fillMaxWidth()
.background(
shape = RoundedCornerShape(14.dp),
color = if (isPressed.value) Color(0xFFE0E0E0) else Color.Transparent
)
.padding(horizontal = 12.dp, vertical = 12.dp)
.pointerInput(Unit) {
detectTapGestures(
onPress = {
isPressed.value = true
tryAwaitRelease()
isPressed.value = false
}
)
}
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
updateLoudSoundReduction(!loudSoundReductionEnabled)
},
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.weight(1f)
.padding(end = 4.dp)
) {
Text(
text = "Loud Sound Reduction",
fontSize = 16.sp,
color = textColor
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Reduces loud sounds you are exposed to.",
fontSize = 12.sp,
color = textColor.copy(0.6f),
lineHeight = 14.sp,
)
}
StyledSwitch(
checked = loudSoundReductionEnabled,
onCheckedChange = {
updateLoudSoundReduction(it)
},
)
}
}
@Preview
@Composable
fun LoudSoundReductionSwitchPreview() {
LoudSoundReductionSwitch(AirPodsService(), LocalContext.current.getSharedPreferences("preview", 0))
}

View File

@@ -0,0 +1,70 @@
package me.kavishdevar.aln.composables
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowRight
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
@Composable
fun NavigationButton(to: String, name: String, navController: NavController) {
Row(
modifier = Modifier
.background(
if (isSystemInDarkTheme()) Color(
0xFF1C1C1E
) else Color(0xFFFFFFFF), RoundedCornerShape(14.dp)
)
.height(55.dp)
.clickable {
navController.navigate(to)
}
) {
Text(
text = name,
modifier = Modifier.padding(16.dp),
color = if (isSystemInDarkTheme()) Color.White else Color.Black
)
Spacer(modifier = Modifier.weight(1f))
IconButton(
onClick = { navController.navigate(to) },
colors = IconButtonDefaults.iconButtonColors(
containerColor = Color.Transparent,
contentColor = if (isSystemInDarkTheme()) Color.White else Color.Black
),
modifier = Modifier
.padding(start = 16.dp)
.fillMaxHeight()
) {
@Suppress("DEPRECATION")
Icon(
imageVector = Icons.Default.KeyboardArrowRight,
contentDescription = name
)
}
}
}
@Preview
@Composable
fun NavigationButtonPreview() {
NavigationButton("to", "Name", NavController(LocalContext.current))
}

View File

@@ -0,0 +1,62 @@
package me.kavishdevar.aln.composables
import me.kavishdevar.aln.R
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Composable
fun NoiseControlButton(
icon: ImageBitmap,
onClick: () -> Unit,
textColor: Color,
backgroundColor: Color,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxHeight()
.padding(horizontal = 4.dp, vertical = 4.dp)
.background(color = backgroundColor, shape = RoundedCornerShape(11.dp))
.clickable(
onClick = onClick,
indication = null,
interactionSource = remember { MutableInteractionSource() }),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Icon(
bitmap = icon,
contentDescription = null,
tint = textColor,
modifier = Modifier.size(40.dp)
)
}
}
@Preview
@Composable
fun NoiseControlButtonPreview() {
NoiseControlButton(
icon = ImageBitmap.imageResource(R.drawable.noise_cancellation),
onClick = {},
textColor = Color.White,
backgroundColor = Color.Black
)
}

View File

@@ -0,0 +1,231 @@
package me.kavishdevar.aln.composables
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.kavishdevar.aln.AirPodsNotifications
import me.kavishdevar.aln.AirPodsService
import me.kavishdevar.aln.NoiseControlMode
import me.kavishdevar.aln.R
@SuppressLint("UnspecifiedRegisterReceiverFlag")
@Composable
fun NoiseControlSettings(service: AirPodsService) {
val isDarkTheme = isSystemInDarkTheme()
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFE3E3E8)
val textColor = if (isDarkTheme) Color.White else Color.Black
val textColorSelected = if (isDarkTheme) Color.White else Color.Black
val selectedBackground = if (isDarkTheme) Color(0xFF5C5A5F) else Color(0xFFFFFFFF)
val noiseControlMode = remember { mutableStateOf(NoiseControlMode.OFF) }
val d1a = remember { mutableFloatStateOf(0f) }
val d2a = remember { mutableFloatStateOf(0f) }
val d3a = remember { mutableFloatStateOf(0f) }
fun onModeSelected(mode: NoiseControlMode, received: Boolean = false) {
noiseControlMode.value = mode
if (!received) service.setANCMode(mode.ordinal+1)
when (mode) {
NoiseControlMode.NOISE_CANCELLATION -> {
d1a.floatValue = 1f
d2a.floatValue = 1f
d3a.floatValue = 0f
}
NoiseControlMode.OFF -> {
d1a.floatValue = 0f
d2a.floatValue = 1f
d3a.floatValue = 1f
}
NoiseControlMode.ADAPTIVE -> {
d1a.floatValue = 1f
d2a.floatValue = 0f
d3a.floatValue = 0f
}
NoiseControlMode.TRANSPARENCY -> {
d1a.floatValue = 0f
d2a.floatValue = 0f
d3a.floatValue = 1f
}
}
}
val noiseControlReceiver = remember {
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == AirPodsNotifications.ANC_DATA) {
noiseControlMode.value = NoiseControlMode.entries.toTypedArray()[intent.getIntExtra("data", 3) - 1]
onModeSelected(noiseControlMode.value, true)
}
else if (intent.action == AirPodsNotifications.DISCONNECT_RECEIVERS) {
context.unregisterReceiver(this)
}
}
}
}
val context = LocalContext.current
val noiseControlIntentFilter = IntentFilter()
.apply {
addAction(AirPodsNotifications.ANC_DATA)
addAction(AirPodsNotifications.DISCONNECT_RECEIVERS)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(noiseControlReceiver, noiseControlIntentFilter, Context.RECEIVER_EXPORTED)
}
else {
context.registerReceiver(noiseControlReceiver, noiseControlIntentFilter)
}
Text(
text = "NOISE CONTROL",
style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Light,
color = textColor.copy(alpha = 0.6f)
),
modifier = Modifier.padding(8.dp, bottom = 2.dp)
)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(75.dp)
.padding(8.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(backgroundColor, RoundedCornerShape(14.dp))
) {
NoiseControlButton(
icon = ImageBitmap.imageResource(R.drawable.noise_cancellation),
onClick = { onModeSelected(NoiseControlMode.OFF) },
textColor = if (noiseControlMode.value == NoiseControlMode.OFF) textColorSelected else textColor,
backgroundColor = if (noiseControlMode.value == NoiseControlMode.OFF) selectedBackground else Color.Transparent,
modifier = Modifier.weight(1f)
)
VerticalDivider(
thickness = 1.dp,
modifier = Modifier
.padding(vertical = 10.dp)
.alpha(d1a.floatValue),
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
)
NoiseControlButton(
icon = ImageBitmap.imageResource(R.drawable.transparency),
onClick = { onModeSelected(NoiseControlMode.TRANSPARENCY) },
textColor = if (noiseControlMode.value == NoiseControlMode.TRANSPARENCY) textColorSelected else textColor,
backgroundColor = if (noiseControlMode.value == NoiseControlMode.TRANSPARENCY) selectedBackground else Color.Transparent,
modifier = Modifier.weight(1f)
)
VerticalDivider(
thickness = 1.dp,
modifier = Modifier
.padding(vertical = 10.dp)
.alpha(d2a.floatValue),
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
)
NoiseControlButton(
icon = ImageBitmap.imageResource(R.drawable.adaptive),
onClick = { onModeSelected(NoiseControlMode.ADAPTIVE) },
textColor = if (noiseControlMode.value == NoiseControlMode.ADAPTIVE) textColorSelected else textColor,
backgroundColor = if (noiseControlMode.value == NoiseControlMode.ADAPTIVE) selectedBackground else Color.Transparent,
modifier = Modifier.weight(1f)
)
VerticalDivider(
thickness = 1.dp,
modifier = Modifier
.padding(vertical = 10.dp)
.alpha(d3a.floatValue),
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
)
NoiseControlButton(
icon = ImageBitmap.imageResource(R.drawable.noise_cancellation),
onClick = { onModeSelected(NoiseControlMode.NOISE_CANCELLATION) },
textColor = if (noiseControlMode.value == NoiseControlMode.NOISE_CANCELLATION) textColorSelected else textColor,
backgroundColor = if (noiseControlMode.value == NoiseControlMode.NOISE_CANCELLATION) selectedBackground else Color.Transparent,
modifier = Modifier.weight(1f)
)
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
.padding(top = 1.dp)
) {
Text(
text = "Off",
style = TextStyle(fontSize = 12.sp, color = textColor),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f)
)
Text(
text = "Transparency",
style = TextStyle(fontSize = 12.sp, color = textColor),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f)
)
Text(
text = "Adaptive",
style = TextStyle(fontSize = 12.sp, color = textColor),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f)
)
Text(
text = "Noise Cancellation",
style = TextStyle(fontSize = 12.sp, color = textColor),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f)
)
}
}
}
@Preview
@Composable
fun NoiseControlSettingsPreview() {
NoiseControlSettings(AirPodsService())
}

View File

@@ -0,0 +1,108 @@
package me.kavishdevar.aln.composables
import android.content.SharedPreferences
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.kavishdevar.aln.AirPodsService
@Composable
fun PersonalizedVolumeSwitch(service: AirPodsService, sharedPreferences: SharedPreferences) {
var personalizedVolumeEnabled by remember {
mutableStateOf(
sharedPreferences.getBoolean("personalized_volume", true)
)
}
fun updatePersonalizedVolume(enabled: Boolean) {
personalizedVolumeEnabled = enabled
sharedPreferences.edit().putBoolean("personalized_volume", enabled).apply()
service.setPVEnabled(enabled)
}
val isDarkTheme = isSystemInDarkTheme()
val textColor = if (isDarkTheme) Color.White else Color.Black
val isPressed = remember { mutableStateOf(false) }
Row(
modifier = Modifier
.fillMaxWidth()
.background(
shape = RoundedCornerShape(14.dp),
color = if (isPressed.value) Color(0xFFE0E0E0) else Color.Transparent
)
.padding(horizontal = 12.dp, vertical = 12.dp)
.pointerInput(Unit) {
detectTapGestures(
onPress = {
isPressed.value = true
tryAwaitRelease()
isPressed.value = false
}
)
}
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
updatePersonalizedVolume(!personalizedVolumeEnabled)
},
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.weight(1f)
.padding(end = 4.dp)
) {
Text(
text = "Personalized Volume",
fontSize = 16.sp,
color = textColor
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Adjusts the volume of media in response to your environment.",
fontSize = 12.sp,
color = textColor.copy(0.6f),
lineHeight = 14.sp,
)
}
StyledSwitch(
checked = personalizedVolumeEnabled,
onCheckedChange = {
updatePersonalizedVolume(it)
},
)
}
}
@Preview
@Composable
fun PersonalizedVolumeSwitchPreview() {
PersonalizedVolumeSwitch(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", 0))
}

View File

@@ -0,0 +1,174 @@
package me.kavishdevar.aln.composables
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import me.kavishdevar.aln.R
@Composable
fun PressAndHoldSettings(navController: NavController) {
val isDarkTheme = isSystemInDarkTheme()
val textColor = if (isDarkTheme) Color.White else Color.Black
Text(
text = "PRESS AND HOLD AIRPODS",
style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Light,
color = textColor.copy(alpha = 0.6f),
fontFamily = FontFamily(Font(R.font.sf_pro))
),
modifier = Modifier.padding(8.dp, bottom = 2.dp)
)
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
Column(
modifier = Modifier
.fillMaxWidth()
.background(backgroundColor, RoundedCornerShape(14.dp))
.padding(top = 2.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(55.dp)
.background(
backgroundColor,
RoundedCornerShape(14.dp)
)
.clickable(
onClick = {
navController.navigate("long_press/Left")
}
),
contentAlignment = Alignment.Center
) {
Row(
modifier = Modifier
.padding(start = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Left",
style = TextStyle(
fontSize = 18.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
),
)
Spacer(modifier = Modifier.weight(1f))
Text(
// TODO: Implement voice assistant on long press; for now, it's noise control
text = "Noise Control",
style = TextStyle(
fontSize = 18.sp,
color = textColor.copy(alpha = 0.6f),
fontFamily = FontFamily(Font(R.font.sf_pro))
),
)
IconButton(
onClick = {
navController.navigate("long_press/Left")
}
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
contentDescription = "go",
tint = textColor
)
}
}
}
HorizontalDivider(
thickness = 2.dp,
color = Color(0xFF4D4D4D).copy(alpha = 0.4f),
modifier = Modifier
.padding(start = 16.dp)
)
Box(
modifier = Modifier
.fillMaxWidth()
.height(55.dp)
.background(
backgroundColor,
RoundedCornerShape(18.dp)
)
.clickable(
onClick = {
navController.navigate("long_press/Right")
}
),
contentAlignment = Alignment.Center
) {
Row(
modifier = Modifier
.padding(start = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Right",
style = TextStyle(
fontSize = 18.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
),
)
Spacer(modifier = Modifier.weight(1f))
Text(
// TODO: Implement voice assistant on long press; for now, it's noise control
text = "Noise Control",
style = TextStyle(
fontSize = 18.sp,
color = textColor.copy(alpha = 0.6f),
fontFamily = FontFamily(Font(R.font.sf_pro))
),
)
IconButton(
onClick = {
navController.navigate("long_press/Right")
}
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
contentDescription = "go",
tint = textColor
)
}
}
}
}
}
@Preview
@Composable
fun PressAndHoldSettingsPreview() {
PressAndHoldSettings(navController = NavController(LocalContext.current))
}

View File

@@ -5,6 +5,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@@ -12,7 +13,6 @@ 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.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -22,8 +22,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
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 me.kavishdevar.aln.AirPodsService import me.kavishdevar.aln.AirPodsService
@@ -42,7 +43,7 @@ fun SinglePodANCSwitch(service: AirPodsService, sharedPreferences: SharedPrefere
service.setNoiseCancellationWithOnePod(enabled) service.setNoiseCancellationWithOnePod(enabled)
} }
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5 val isDarkTheme = isSystemInDarkTheme()
val textColor = if (isDarkTheme) Color.White else Color.Black val textColor = if (isDarkTheme) Color.White else Color.Black
val isPressed = remember { mutableStateOf(false) } val isPressed = remember { mutableStateOf(false) }
@@ -98,3 +99,9 @@ fun SinglePodANCSwitch(service: AirPodsService, sharedPreferences: SharedPrefere
) )
} }
} }
@Preview
@Composable
fun SinglePodANCSwitchPreview() {
SinglePodANCSwitch(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", 0))
}

View File

@@ -3,6 +3,7 @@ package me.kavishdevar.aln.composables
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
@@ -11,14 +12,13 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@Composable @Composable
@@ -26,7 +26,7 @@ fun StyledSwitch(
checked: Boolean, checked: Boolean,
onCheckedChange: (Boolean) -> Unit onCheckedChange: (Boolean) -> Unit
) { ) {
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5 val isDarkTheme = isSystemInDarkTheme()
val thumbColor = Color.White val thumbColor = Color.White
val trackColor = if (checked) Color(0xFF34C759) else if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6) val trackColor = if (checked) Color(0xFF34C759) else if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6)
@@ -53,3 +53,9 @@ fun StyledSwitch(
) )
} }
} }
@Preview
@Composable
fun StyledSwitchPreview() {
StyledSwitch(checked = true, onCheckedChange = {})
}

View File

@@ -1,6 +1,7 @@
package me.kavishdevar.aln.composables package me.kavishdevar.aln.composables
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@@ -8,7 +9,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -20,8 +20,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -33,7 +33,7 @@ fun StyledTextField(
) { ) {
var isFocused by remember { mutableStateOf(false) } var isFocused by remember { mutableStateOf(false) }
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5 val isDarkTheme = isSystemInDarkTheme()
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
val textColor = if (isDarkTheme) Color.White else Color.Black val textColor = if (isDarkTheme) Color.White else Color.Black
@@ -87,3 +87,9 @@ fun StyledTextField(
) )
} }
} }
@Preview
@Composable
fun StyledTextFieldPreview() {
StyledTextField(name = "Name", value = "AirPods Pro", onValueChange = {})
}

View File

@@ -2,6 +2,7 @@ package me.kavishdevar.aln.composables
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.compose.foundation.background import androidx.compose.foundation.background
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.Row import androidx.compose.foundation.layout.Row
@@ -12,7 +13,6 @@ 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
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider 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
@@ -24,11 +24,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle 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.tooling.preview.Preview
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 me.kavishdevar.aln.AirPodsService import me.kavishdevar.aln.AirPodsService
@@ -48,7 +49,7 @@ fun ToneVolumeSlider(service: AirPodsService, sharedPreferences: SharedPreferenc
sharedPreferences.edit().putInt("tone_volume", sliderValue.floatValue.toInt()).apply() sharedPreferences.edit().putInt("tone_volume", sliderValue.floatValue.toInt()).apply()
} }
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5 val isDarkTheme = isSystemInDarkTheme()
val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFF929491) val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFF929491)
val activeTrackColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) val activeTrackColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5)
@@ -133,3 +134,9 @@ fun ToneVolumeSlider(service: AirPodsService, sharedPreferences: SharedPreferenc
) )
} }
} }
@Preview
@Composable
fun ToneVolumeSliderPreview() {
ToneVolumeSlider(AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", 0))
}

View File

@@ -5,6 +5,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@@ -12,7 +13,6 @@ 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.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -22,8 +22,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
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 me.kavishdevar.aln.AirPodsService import me.kavishdevar.aln.AirPodsService
@@ -41,7 +42,7 @@ fun VolumeControlSwitch(service: AirPodsService, sharedPreferences: SharedPrefer
service.setVolumeControl(enabled) service.setVolumeControl(enabled)
} }
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5 val isDarkTheme = isSystemInDarkTheme()
val textColor = if (isDarkTheme) Color.White else Color.Black val textColor = if (isDarkTheme) Color.White else Color.Black
val isPressed = remember { mutableStateOf(false) } val isPressed = remember { mutableStateOf(false) }
@@ -97,3 +98,9 @@ fun VolumeControlSwitch(service: AirPodsService, sharedPreferences: SharedPrefer
) )
} }
} }
@Preview
@Composable
fun VolumeControlSwitchPreview() {
VolumeControlSwitch(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", 0))
}

View File

@@ -1,62 +0,0 @@
package me.kavishdevar.aln
import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
class BluetoothReceiver : BroadcastReceiver() {
fun onConnect(bluetoothDevice: BluetoothDevice?) {
}
fun onDisconnect(bluetoothDevice: BluetoothDevice?) {
}
@SuppressLint("NewApi")
override fun onReceive(context: Context?, intent: Intent) {
val bluetoothDevice =
intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE", BluetoothDevice::class.java)
val action = intent.action
// Airpods filter
if (bluetoothDevice != null && action != null && !action.isEmpty()) {
// Airpods connected, show notification.
if (BluetoothDevice.ACTION_ACL_CONNECTED == action) {
onConnect(bluetoothDevice)
}
// Airpods disconnected, remove notification but leave the scanner going.
if (BluetoothDevice.ACTION_ACL_DISCONNECTED == action
|| BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED == action
) {
onDisconnect(bluetoothDevice)
}
}
}
companion object {
/**
* When the service is created, we register to get as many bluetooth and airpods related events as possible.
* ACL_CONNECTED and ACL_DISCONNECTED should have been enough, but you never know with android these days.
*/
fun buildFilter(): IntentFilter {
val intentFilter = IntentFilter()
intentFilter.addAction("android.bluetooth.device.action.ACL_CONNECTED")
intentFilter.addAction("android.bluetooth.device.action.ACL_DISCONNECTED")
intentFilter.addAction("android.bluetooth.device.action.BOND_STATE_CHANGED")
intentFilter.addAction("android.bluetooth.device.action.NAME_CHANGED")
intentFilter.addAction("android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED")
intentFilter.addAction("android.bluetooth.adapter.action.STATE_CHANGED")
intentFilter.addAction("android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED")
intentFilter.addAction("android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT")
intentFilter.addAction("android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED")
intentFilter.addAction("android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED")
intentFilter.addCategory("android.bluetooth.headset.intent.category.companyid.76")
return intentFilter
}
}
}

View File

@@ -0,0 +1,25 @@
package me.kavishdevar.aln.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import me.kavishdevar.aln.AirPodsService
class BootReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
Intent.ACTION_MY_PACKAGE_REPLACED -> try { context?.startForegroundService(
Intent(
context,
AirPodsService::class.java
)
) } catch (e: Exception) { e.printStackTrace() }
Intent.ACTION_BOOT_COMPLETED -> try { context?.startForegroundService(
Intent(
context,
AirPodsService::class.java
)
) } catch (e: Exception) { e.printStackTrace() }
}
}
}

View File

@@ -0,0 +1,261 @@
package me.kavishdevar.aln.screens
import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.content.Context.MODE_PRIVATE
import android.content.Intent
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.haze
import dev.chrisbanes.haze.hazeChild
import dev.chrisbanes.haze.materials.CupertinoMaterials
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import me.kavishdevar.aln.AirPodsNotifications
import me.kavishdevar.aln.AirPodsService
import me.kavishdevar.aln.composables.AccessibilitySettings
import me.kavishdevar.aln.composables.AudioSettings
import me.kavishdevar.aln.composables.BatteryView
import me.kavishdevar.aln.composables.IndependentToggle
import me.kavishdevar.aln.composables.NavigationButton
import me.kavishdevar.aln.composables.NoiseControlSettings
import me.kavishdevar.aln.composables.PressAndHoldSettings
import me.kavishdevar.aln.composables.StyledTextField
import me.kavishdevar.aln.ui.theme.ALNTheme
@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
@SuppressLint("MissingPermission", "NewApi")
@Composable
fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
navController: NavController, isConnected: Boolean) {
val sharedPreferences = LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE)
var device by remember { mutableStateOf(dev) }
var deviceName by remember {
mutableStateOf(
TextFieldValue(
sharedPreferences.getString("name", device?.name ?: "AirPods Pro").toString()
)
)
}
val verticalScrollState = rememberScrollState()
val hazeState = remember { HazeState() }
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
Scaffold(
containerColor = if (isSystemInDarkTheme()) Color(
0xFF000000
) else Color(
0xFFF2F2F7
),
topBar = {
val darkMode = isSystemInDarkTheme()
val mDensity = remember { mutableFloatStateOf(1f) }
CenterAlignedTopAppBar(
title = {
Text(
text = deviceName.text
)
},
modifier = Modifier
.hazeChild(
state = hazeState,
style = CupertinoMaterials.regular(),
block = {
alpha =
if (verticalScrollState.value > 55.dp.value * mDensity.floatValue) 1f else 0f
}
)
.drawBehind {
mDensity.floatValue = density
val strokeWidth = 0.7.dp.value * density
val y = size.height - strokeWidth / 2
if (verticalScrollState.value > 55.dp.value * density) {
drawLine(
if (darkMode) Color.DarkGray else Color.LightGray,
Offset(0f, y),
Offset(size.width, y),
strokeWidth
)
}
},
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent
),
// actions = {
// val context = LocalContext.current
// IconButton(
// onClick = {
// ServiceManager.restartService(context)
// },
// colors = IconButtonDefaults.iconButtonColors(
// containerColor = Color.Transparent,
// contentColor = if (isSystemInDarkTheme()) Color.White else Color.Black
// )
// ) {
// Icon(
// imageVector = Icons.Default.Refresh,
// contentDescription = "Settings",
// )
// }
// }
)
}
) { paddingValues ->
if (isConnected == true) {
Column(
modifier = Modifier
.haze(hazeState)
.fillMaxSize()
.padding(horizontal = 16.dp)
.verticalScroll(
state = verticalScrollState,
enabled = true,
)
) {
Spacer(Modifier.height(75.dp))
LaunchedEffect(service) {
service.let {
it.sendBroadcast(Intent(AirPodsNotifications.Companion.BATTERY_DATA).apply {
putParcelableArrayListExtra("data", ArrayList(it.getBattery()))
})
it.sendBroadcast(Intent(AirPodsNotifications.Companion.ANC_DATA).apply {
putExtra("data", it.getANC())
})
}
}
val sharedPreferences = LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE)
Spacer(modifier = Modifier.height(64.dp))
BatteryView(service = service)
Spacer(modifier = Modifier.height(32.dp))
StyledTextField(
name = "Name",
value = deviceName.text,
onValueChange = {
deviceName = TextFieldValue(it)
sharedPreferences.edit().putString("name", it).apply()
service.setName(it)
}
)
Spacer(modifier = Modifier.height(32.dp))
NoiseControlSettings(service = service)
Spacer(modifier = Modifier.height(16.dp))
PressAndHoldSettings(navController = navController)
Spacer(modifier = Modifier.height(16.dp))
AudioSettings(service = service, sharedPreferences = sharedPreferences)
Spacer(modifier = Modifier.height(16.dp))
IndependentToggle(
name = "Automatic Ear Detection",
service = service,
functionName = "setEarDetection",
sharedPreferences = sharedPreferences,
true
)
Spacer(modifier = Modifier.height(16.dp))
IndependentToggle(
name = "Off Listening Mode",
service = service,
functionName = "setOffListeningMode",
sharedPreferences = sharedPreferences,
false
)
Spacer(modifier = Modifier.height(16.dp))
AccessibilitySettings(service = service, sharedPreferences = sharedPreferences)
Spacer(modifier = Modifier.height(16.dp))
NavigationButton("debug", "Debug", navController)
Spacer(Modifier.height(24.dp))
}
}
else {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 8.dp)
.verticalScroll(
state = verticalScrollState,
enabled = true,
),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "AirPods not connected",
style = TextStyle(
fontSize = 24.sp,
fontWeight = FontWeight.Medium,
color = if (isSystemInDarkTheme()) Color.White else Color.Black
),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.height(24.dp))
Text(
text = "Please connect your AirPods to access settings. If you're stuck here, then try reopening the app again after closing it from the recents.\n(DO NOT KILL THE APP!)",
style = TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.Light,
color = if (isSystemInDarkTheme()) Color.White else Color.Black
),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
@Preview
@Composable
fun AirPodsSettingsScreenPreview() {
ALNTheme (
darkTheme = true
) {
AirPodsSettingsScreen(dev = null, service = AirPodsService(), navController = rememberNavController(), isConnected = true)
}
}

View File

@@ -1,6 +1,6 @@
@file:OptIn(ExperimentalHazeMaterialsApi::class) @file:OptIn(ExperimentalHazeMaterialsApi::class)
package me.kavishdevar.aln package me.kavishdevar.aln.screens
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
@@ -13,6 +13,7 @@ import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import androidx.compose.foundation.background import androidx.compose.foundation.background
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
@@ -34,7 +35,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
@@ -49,7 +49,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
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
@@ -62,6 +61,9 @@ import dev.chrisbanes.haze.haze
import dev.chrisbanes.haze.hazeChild import dev.chrisbanes.haze.hazeChild
import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.CupertinoMaterials
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import me.kavishdevar.aln.AirPodsNotifications
import me.kavishdevar.aln.AirPodsService
import me.kavishdevar.aln.R
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@@ -97,10 +99,10 @@ fun DebugScreen(navController: NavController) {
), ),
colors = TopAppBarDefaults.topAppBarColors( colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent containerColor = Color.Transparent
) ),
) )
}, },
containerColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(0xFF000000) containerColor = if (isSystemInDarkTheme()) Color(0xFF000000)
else Color(0xFFF2F2F7), else Color(0xFFF2F2F7),
) { paddingValues -> ) { paddingValues ->
val receiver = object : BroadcastReceiver() { val receiver = object : BroadcastReceiver() {
@@ -113,7 +115,7 @@ fun DebugScreen(navController: NavController) {
} }
LaunchedEffect(context) { LaunchedEffect(context) {
val intentFilter = IntentFilter(AirPodsNotifications.AIRPODS_DATA) val intentFilter = IntentFilter(AirPodsNotifications.Companion.AIRPODS_DATA)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED) context.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED)
} }
@@ -166,7 +168,7 @@ fun DebugScreen(navController: NavController) {
Text( Text(
text = if (isSent) message.substring(1) else message, text = if (isSent) message.substring(1) else message,
fontFamily = FontFamily(Font(R.font.hack)), fontFamily = FontFamily(Font(R.font.hack)),
color = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color( color = if (isSystemInDarkTheme()) Color(
0xFF000000 0xFF000000
) )
else Color(0xFF000000), else Color(0xFF000000),
@@ -202,7 +204,7 @@ fun DebugScreen(navController: NavController) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.background(if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(0xFF1C1B20) else Color(0xFFF2F2F7)), .background(if (isSystemInDarkTheme()) Color(0xFF1C1B20) else Color(0xFFF2F2F7)),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
val packet = remember { mutableStateOf(TextFieldValue("")) } val packet = remember { mutableStateOf(TextFieldValue("")) }
@@ -227,14 +229,14 @@ fun DebugScreen(navController: NavController) {
} }
}, },
colors = TextFieldDefaults.colors( colors = TextFieldDefaults.colors(
focusedContainerColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(0xFF1C1B20) else Color(0xFFF2F2F7), focusedContainerColor = if (isSystemInDarkTheme()) Color(0xFF1C1B20) else Color(0xFFF2F2F7),
unfocusedContainerColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(0xFF1C1B20) else Color(0xFFF2F2F7), unfocusedContainerColor = if (isSystemInDarkTheme()) Color(0xFF1C1B20) else Color(0xFFF2F2F7),
focusedIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent,
focusedTextColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color.White else Color.Black, focusedTextColor = if (isSystemInDarkTheme()) Color.White else Color.Black,
unfocusedTextColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color.White else Color.Black.copy(alpha = 0.6f), unfocusedTextColor = if (isSystemInDarkTheme()) Color.White else Color.Black.copy(alpha = 0.6f),
focusedLabelColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color.White.copy(alpha = 0.6f) else Color.Black, focusedLabelColor = if (isSystemInDarkTheme()) Color.White.copy(alpha = 0.6f) else Color.Black,
unfocusedLabelColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.6f), unfocusedLabelColor = if (isSystemInDarkTheme()) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.6f),
), ),
shape = RoundedCornerShape(12.dp) shape = RoundedCornerShape(12.dp)
) )

View File

@@ -1,9 +1,10 @@
package me.kavishdevar.aln package me.kavishdevar.aln.screens
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
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
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -21,7 +22,6 @@ import androidx.compose.material3.CheckboxDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
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
@@ -35,7 +35,6 @@ 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
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.imageResource import androidx.compose.ui.res.imageResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
@@ -45,6 +44,8 @@ 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.navigation.NavController import androidx.navigation.NavController
import me.kavishdevar.aln.R
import me.kavishdevar.aln.ServiceManager
@Composable() @Composable()
fun RightDivider() { fun RightDivider() {
@@ -64,7 +65,7 @@ fun LongPress(navController: NavController, name: String) {
val transparencyChecked = remember { mutableStateOf(sharedPreferences.getBoolean("long_press_transparency", false)) } val transparencyChecked = remember { mutableStateOf(sharedPreferences.getBoolean("long_press_transparency", false)) }
val adaptiveChecked = remember { mutableStateOf(sharedPreferences.getBoolean("long_press_adaptive", false)) } val adaptiveChecked = remember { mutableStateOf(sharedPreferences.getBoolean("long_press_adaptive", false)) }
Log.d("LongPress", "offChecked: ${offChecked.value}, ncChecked: ${ncChecked.value}, transparencyChecked: ${transparencyChecked.value}, adaptiveChecked: ${adaptiveChecked.value}") Log.d("LongPress", "offChecked: ${offChecked.value}, ncChecked: ${ncChecked.value}, transparencyChecked: ${transparencyChecked.value}, adaptiveChecked: ${adaptiveChecked.value}")
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5 val isDarkTheme = isSystemInDarkTheme()
val textColor = if (isDarkTheme) Color.White else Color.Black val textColor = if (isDarkTheme) Color.White else Color.Black
Scaffold( Scaffold(
@@ -81,7 +82,7 @@ fun LongPress(navController: NavController, name: String) {
onClick = { onClick = {
navController.popBackStack() navController.popBackStack()
}, },
shape = RoundedCornerShape(24.dp), shape = RoundedCornerShape(8.dp),
) { ) {
Icon( Icon(
Icons.AutoMirrored.Filled.KeyboardArrowLeft, Icons.AutoMirrored.Filled.KeyboardArrowLeft,
@@ -105,7 +106,7 @@ fun LongPress(navController: NavController, name: String) {
) )
) )
}, },
containerColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(0xFF000000) containerColor = if (isSystemInDarkTheme()) Color(0xFF000000)
else Color(0xFFF2F2F7), else Color(0xFFF2F2F7),
) { paddingValues -> ) { paddingValues ->
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
@@ -160,7 +161,7 @@ fun LongPressElement (name: String, checked: MutableState<Boolean>, id: String,
val sharedPreferences = val sharedPreferences =
LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
val offListeningMode = sharedPreferences.getBoolean("off_listening_mode", false) val offListeningMode = sharedPreferences.getBoolean("off_listening_mode", false)
val darkMode = MaterialTheme.colorScheme.surface.luminance() < 0.5 val darkMode = isSystemInDarkTheme()
val textColor = if (darkMode) Color.White else Color.Black val textColor = if (darkMode) Color.White else Color.Black
val desc = when (name) { val desc = when (name) {
"Off" -> "Turns off noise management" "Off" -> "Turns off noise management"

View File

@@ -1,4 +1,4 @@
package me.kavishdevar.aln package me.kavishdevar.aln.services
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
@@ -8,6 +8,9 @@ import android.content.IntentFilter
import android.service.quicksettings.Tile import android.service.quicksettings.Tile
import android.service.quicksettings.TileService import android.service.quicksettings.TileService
import android.util.Log import android.util.Log
import me.kavishdevar.aln.AirPodsNotifications
import me.kavishdevar.aln.NoiseControlMode
import me.kavishdevar.aln.ServiceManager
class AirPodsQSService: TileService() { class AirPodsQSService: TileService() {
private val ancModes = listOf(NoiseControlMode.NOISE_CANCELLATION.name, NoiseControlMode.TRANSPARENCY.name, NoiseControlMode.ADAPTIVE.name) private val ancModes = listOf(NoiseControlMode.NOISE_CANCELLATION.name, NoiseControlMode.TRANSPARENCY.name, NoiseControlMode.ADAPTIVE.name)
@@ -46,18 +49,19 @@ class AirPodsQSService: TileService() {
availabilityReceiver = object : BroadcastReceiver() { availabilityReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
if (intent.action == AirPodsNotifications.AIRPODS_CONNECTED) { if (intent.action == AirPodsNotifications.Companion.AIRPODS_CONNECTED) {
qsTile.state = Tile.STATE_ACTIVE qsTile.state = Tile.STATE_ACTIVE
qsTile.updateTile() qsTile.updateTile()
} }
else if (intent.action == AirPodsNotifications.AIRPODS_DISCONNECTED) { else if (intent.action == AirPodsNotifications.Companion.AIRPODS_DISCONNECTED) {
qsTile.state = Tile.STATE_UNAVAILABLE qsTile.state = Tile.STATE_UNAVAILABLE
qsTile.updateTile() qsTile.updateTile()
} }
} }
} }
registerReceiver(ancStatusReceiver, IntentFilter(AirPodsNotifications.ANC_DATA), RECEIVER_EXPORTED) registerReceiver(ancStatusReceiver,
IntentFilter(AirPodsNotifications.Companion.ANC_DATA), RECEIVER_EXPORTED)
qsTile.state = if (ServiceManager.getService()?.isConnected == true) Tile.STATE_ACTIVE else Tile.STATE_UNAVAILABLE qsTile.state = if (ServiceManager.getService()?.isConnected == true) Tile.STATE_ACTIVE else Tile.STATE_UNAVAILABLE
val ancIndex = ServiceManager.getService()?.getANC() val ancIndex = ServiceManager.getService()?.getANC()

View File

@@ -1,4 +1,4 @@
package me.kavishdevar.aln package me.kavishdevar.aln.utils
import android.media.AudioManager import android.media.AudioManager
import android.util.Log import android.util.Log
@@ -16,16 +16,36 @@ object MediaController {
@Synchronized @Synchronized
fun sendPause() { fun sendPause() {
if (audioManager.isMusicActive) { if (audioManager.isMusicActive) {
audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE)) audioManager.dispatchMediaKeyEvent(
audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PAUSE)) KeyEvent(
KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_MEDIA_PAUSE
)
)
audioManager.dispatchMediaKeyEvent(
KeyEvent(
KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_MEDIA_PAUSE
)
)
} }
} }
@Synchronized @Synchronized
fun sendPlay() { fun sendPlay() {
if (!audioManager.isMusicActive) { if (!audioManager.isMusicActive) {
audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY)) audioManager.dispatchMediaKeyEvent(
audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY)) KeyEvent(
KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_MEDIA_PLAY
)
)
audioManager.dispatchMediaKeyEvent(
KeyEvent(
KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_MEDIA_PLAY
)
)
} }
} }