fix for pre-tiramisu android versions

This commit is contained in:
Kavish Devar
2025-01-07 23:22:16 +05:30
parent c6863a8d2c
commit fda343ca39
8 changed files with 135 additions and 38 deletions

View File

@@ -18,6 +18,7 @@
package me.kavishdevar.aln
import android.Manifest
import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothDevice.TRANSPORT_LE
@@ -25,11 +26,13 @@ import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresPermission
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
@@ -49,7 +52,7 @@ import org.lsposed.hiddenapibypass.HiddenApiBypass
import java.util.UUID
class CustomDevice : ComponentActivity() {
@SuppressLint("MissingPermission", "CoroutineCreationDuringComposition", "NewApi")
@SuppressLint("MissingPermission", "CoroutineCreationDuringComposition")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
@@ -152,7 +155,7 @@ class CustomDevice : ComponentActivity() {
}
}
@SuppressLint("MissingPermission", "NewApi")
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
fun sendWriteRequest(
gatt: BluetoothGatt,
characteristicUuid: String,
@@ -177,6 +180,10 @@ fun sendWriteRequest(
// Send the write request
val success = gatt.writeCharacteristic(characteristic, value, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
val success = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
gatt.writeCharacteristic(characteristic, value, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
} else {
gatt.writeCharacteristic(characteristic)
}
Log.d("GATT", "Write request sent $success to UUID: $characteristicUuid")
}

View File

@@ -22,9 +22,11 @@ import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Context.RECEIVER_EXPORTED
import android.content.Intent
import android.content.IntentFilter
import android.content.ServiceConnection
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.util.Log
@@ -110,7 +112,7 @@ class MainActivity : ComponentActivity() {
}
}
@SuppressLint("MissingPermission", "InlinedApi")
@SuppressLint("MissingPermission", "InlinedApi", "UnspecifiedRegisterReceiverFlag")
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun Main() {
@@ -146,7 +148,12 @@ fun Main() {
addAction(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED)
}
Log.d("MainActivity", "Registering Receiver")
context.registerReceiver(connectionStatusReceiver, filter, Context.RECEIVER_EXPORTED)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
context.registerReceiver(connectionStatusReceiver, filter, RECEIVER_EXPORTED)
} else {
context.registerReceiver(connectionStatusReceiver, filter)
}
Log.d("MainActivity", "Registered Receiver")
NavHost(

View File

@@ -23,20 +23,29 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.os.Build
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
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.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
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.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -50,19 +59,36 @@ 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.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.kavishdevar.aln.R
import me.kavishdevar.aln.services.AirPodsService
import me.kavishdevar.aln.utils.AirPodsNotifications
import me.kavishdevar.aln.utils.NoiseControlMode
import kotlin.math.roundToInt
@SuppressLint("UnspecifiedRegisterReceiverFlag")
@Composable
fun NoiseControlSettings(service: AirPodsService) {
val context = LocalContext.current
val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
val offListeningMode = sharedPreferences.getBoolean("off_listening_mode", true)
val offListeningMode = remember { mutableStateOf(sharedPreferences.getBoolean("off_listening_mode", true)) }
val preferenceChangeListener = remember {
SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
if (key == "off_listening_mode") {
offListeningMode.value = sharedPreferences.getBoolean("off_listening_mode", true)
}
}
}
DisposableEffect(Unit) {
sharedPreferences.registerOnSharedPreferenceChangeListener(preferenceChangeListener)
onDispose {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener)
}
}
val isDarkTheme = isSystemInDarkTheme()
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFE3E3E8)
@@ -71,18 +97,28 @@ fun NoiseControlSettings(service: AirPodsService) {
val selectedBackground = if (isDarkTheme) Color(0xFF5C5A5F) else Color(0xFFFFFFFF)
val noiseControlMode = remember { mutableStateOf(NoiseControlMode.OFF) }
val selectedOffset = remember { mutableFloatStateOf(0f) }
val animatedOffset = animateFloatAsState(targetValue = selectedOffset.floatValue)
val d1a = remember { mutableFloatStateOf(0f) }
val d2a = remember { mutableFloatStateOf(0f) }
val d3a = remember { mutableFloatStateOf(0f) }
val boxWidth = 200.dp.value
fun onModeSelected(mode: NoiseControlMode, received: Boolean = false) {
if (!received && !offListeningMode && mode == NoiseControlMode.OFF) {
if (!received && !offListeningMode.value && mode == NoiseControlMode.OFF) {
noiseControlMode.value = NoiseControlMode.ADAPTIVE
} else {
noiseControlMode.value = mode
}
if (!received) service.setANCMode(mode.ordinal + 1)
selectedOffset.floatValue = when (noiseControlMode.value) {
NoiseControlMode.NOISE_CANCELLATION -> 3 * boxWidth
NoiseControlMode.OFF -> 0f
NoiseControlMode.ADAPTIVE -> 2 * boxWidth
NoiseControlMode.TRANSPARENCY -> 1 * boxWidth
}
when (noiseControlMode.value) {
NoiseControlMode.NOISE_CANCELLATION -> {
d1a.floatValue = 1f
@@ -107,6 +143,15 @@ fun NoiseControlSettings(service: AirPodsService) {
}
}
LaunchedEffect(noiseControlMode.value) {
selectedOffset.value = when (noiseControlMode.value) {
NoiseControlMode.NOISE_CANCELLATION -> 3 * boxWidth
NoiseControlMode.OFF -> 0f
NoiseControlMode.ADAPTIVE -> 2 * boxWidth
NoiseControlMode.TRANSPARENCY -> 1 * boxWidth
}
}
val noiseControlReceiver = remember {
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
@@ -154,13 +199,26 @@ fun NoiseControlSettings(service: AirPodsService) {
.fillMaxWidth()
.height(75.dp)
.padding(8.dp)
.draggable(
orientation = Orientation.Horizontal,
state = rememberDraggableState { delta ->
selectedOffset.floatValue = (selectedOffset.floatValue + delta).coerceIn(0f, 3 * boxWidth)
}
)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(backgroundColor, RoundedCornerShape(14.dp))
) {
if (offListeningMode) {
Box(
modifier = Modifier
.offset { IntOffset(animatedOffset.value.roundToInt(), 0) }
.width(boxWidth.dp)
.height(75.dp)
.background(selectedBackground, RoundedCornerShape(14.dp))
)
if (offListeningMode.value) {
NoiseControlButton(
icon = ImageBitmap.imageResource(R.drawable.noise_cancellation),
onClick = { onModeSelected(NoiseControlMode.OFF) },
@@ -219,7 +277,7 @@ fun NoiseControlSettings(service: AirPodsService) {
.padding(horizontal = 8.dp)
.padding(top = 1.dp)
) {
if (offListeningMode) {
if (offListeningMode.value) {
Text(
text = "Off",
style = TextStyle(fontSize = 12.sp, color = textColor),

View File

@@ -86,7 +86,7 @@ import me.kavishdevar.aln.ui.theme.ALNTheme
import me.kavishdevar.aln.utils.AirPodsNotifications
@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
@SuppressLint("MissingPermission", "NewApi")
@SuppressLint("MissingPermission")
@Composable
fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
navController: NavController, isConnected: Boolean) {

View File

@@ -88,7 +88,7 @@ import me.kavishdevar.aln.services.AirPodsService
import me.kavishdevar.aln.utils.AirPodsNotifications
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "UnspecifiedRegisterReceiverFlag")
@Composable
fun DebugScreen(navController: NavController) {
val hazeState = remember { HazeState() }
@@ -158,6 +158,8 @@ fun DebugScreen(navController: NavController) {
val intentFilter = IntentFilter(AirPodsNotifications.Companion.AIRPODS_DATA)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED)
} else {
context.registerReceiver(receiver, intentFilter)
}
}

View File

@@ -23,6 +23,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import android.util.Log
@@ -35,7 +36,7 @@ class AirPodsQSService: TileService() {
private lateinit var ancStatusReceiver: BroadcastReceiver
private lateinit var availabilityReceiver: BroadcastReceiver
@SuppressLint("InlinedApi")
@SuppressLint("InlinedApi", "UnspecifiedRegisterReceiverFlag")
override fun onStartListening() {
super.onStartListening()
currentModeIndex = (ServiceManager.getService()?.getANC()?.minus(1)) ?: -1
@@ -77,9 +78,17 @@ class AirPodsQSService: TileService() {
}
}
registerReceiver(ancStatusReceiver,
IntentFilter(AirPodsNotifications.Companion.ANC_DATA), RECEIVER_EXPORTED)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(
ancStatusReceiver,
IntentFilter(AirPodsNotifications.Companion.ANC_DATA), RECEIVER_EXPORTED
)
} else {
registerReceiver(
ancStatusReceiver,
IntentFilter(AirPodsNotifications.Companion.ANC_DATA)
)
}
qsTile.state = if (ServiceManager.getService()?.isConnected == true) Tile.STATE_ACTIVE else Tile.STATE_UNAVAILABLE
val ancIndex = ServiceManager.getService()?.getANC()
currentModeIndex = if (ancIndex != null) { if (ancIndex == 2) 0 else if (ancIndex == 3) 1 else if (ancIndex == 4) 2 else 2 } else 0

View File

@@ -107,10 +107,14 @@ class AirPodsService: Service() {
@Suppress("ClassName")
private object bluetoothReceiver: BroadcastReceiver() {
@SuppressLint("NewApi", "MissingPermission")
@SuppressLint("MissingPermission")
override fun onReceive(context: Context?, intent: Intent) {
val bluetoothDevice =
intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE", BluetoothDevice::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE", BluetoothDevice::class.java)
} else {
intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE") as BluetoothDevice?
}
val action = intent.action
val context = context?.applicationContext
val name = context?.getSharedPreferences("settings", MODE_PRIVATE)?.getString("name", bluetoothDevice?.name)
@@ -148,7 +152,7 @@ class AirPodsService: Service() {
notificationManager.createNotificationChannel(notificationChannel)
val notification = NotificationCompat.Builder(this, "background_service_status")
.setSmallIcon(R.drawable.airpods)
.setContentTitle("AirPods are not connected")
.setContentTitle("AirPods not connected")
.setCategory(Notification.CATEGORY_SERVICE)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(true)
@@ -261,24 +265,24 @@ class AirPodsService: Service() {
updatedNotification = NotificationCompat.Builder(this, "background_service_status")
.setSmallIcon(R.drawable.airpods)
.setContentTitle("""AirPods ${batteryList?.find { it.component == BatteryComponent.LEFT }?.let {
// if (it.status != BatteryStatus.DISCONNECTED) {
.setContentTitle("""$airpodsName ${batteryList?.find { it.component == BatteryComponent.LEFT }?.let {
if (it.status != BatteryStatus.DISCONNECTED) {
" L:${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
// } else {
// ""
// }
} else {
""
}
} ?: ""}${batteryList?.find { it.component == BatteryComponent.RIGHT }?.let {
// if (it.status != BatteryStatus.DISCONNECTED) {
if (it.status != BatteryStatus.DISCONNECTED) {
" R:${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
// } else {
// ""
// }
} else {
""
}
} ?: ""}${batteryList?.find { it.component == BatteryComponent.CASE }?.let {
// if (it.status != BatteryStatus.DISCONNECTED) {
if (it.status != BatteryStatus.DISCONNECTED) {
" C:${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
// } else {
// ""
// }
} else {
""
}
} ?: ""}""")
.setCategory(Notification.CATEGORY_SERVICE)
.setPriority(NotificationCompat.PRIORITY_LOW)
@@ -288,7 +292,7 @@ class AirPodsService: Service() {
} else {
updatedNotification = NotificationCompat.Builder(this, "background_service_status")
.setSmallIcon(R.drawable.airpods)
.setContentTitle("AirPods are not connected")
.setContentTitle("AirPods not connected")
.setCategory(Notification.CATEGORY_SERVICE)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(true)
@@ -301,7 +305,7 @@ class AirPodsService: Service() {
private lateinit var connectionReceiver: BroadcastReceiver
private lateinit var disconnectionReceiver: BroadcastReceiver
@SuppressLint("InlinedApi", "MissingPermission")
@SuppressLint("InlinedApi", "MissingPermission", "UnspecifiedRegisterReceiverFlag")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("AirPodsService", "Service started")
ServiceManager.setService(this)
@@ -319,12 +323,20 @@ class AirPodsService: Service() {
addAction("android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED")
}
registerReceiver(bluetoothReceiver, serviceIntentFilter, RECEIVER_EXPORTED)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(bluetoothReceiver, serviceIntentFilter, RECEIVER_EXPORTED)
} else {
registerReceiver(bluetoothReceiver, serviceIntentFilter)
}
connectionReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == AirPodsNotifications.Companion.AIRPODS_CONNECTION_DETECTED) {
device = intent.getParcelableExtra("device", BluetoothDevice::class.java)!!
device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra("device", BluetoothDevice::class.java)!!
} else {
intent.getParcelableExtra("device") as BluetoothDevice?
}
val name = this@AirPodsService.getSharedPreferences("settings", MODE_PRIVATE)
.getString("name", device?.name)
if (this@AirPodsService.getSharedPreferences("settings", MODE_PRIVATE).getString("name", null) == null) {
@@ -349,7 +361,11 @@ class AirPodsService: Service() {
addAction(AirPodsNotifications.Companion.AIRPODS_CONNECTION_DETECTED)
addAction(AirPodsNotifications.Companion.AIRPODS_DISCONNECTED)
}
registerReceiver(connectionReceiver, deviceIntentFilter, RECEIVER_EXPORTED)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(connectionReceiver, deviceIntentFilter, RECEIVER_EXPORTED)
} else {
registerReceiver(connectionReceiver, deviceIntentFilter)
}
val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter
bluetoothAdapter.bondedDevices.forEach { device ->
@@ -500,7 +516,6 @@ class AirPodsService: Service() {
)
var justEnabledA2dp = false
earReceiver = object : BroadcastReceiver() {
@SuppressLint("NewApi")
override fun onReceive(context: Context, intent: Intent) {
val data = intent.getByteArrayExtra("data")
if (data != null && earDetectionEnabled) {

View File

@@ -48,7 +48,6 @@ class Window (context: Context) {
private val mView: View
@Suppress("DEPRECATION")
@SuppressLint("NewApi")
private val mParams: WindowManager.LayoutParams = WindowManager.LayoutParams().apply {
height = WindowManager.LayoutParams.WRAP_CONTENT
width = WindowManager.LayoutParams.MATCH_PARENT