mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-29 01:29:49 +00:00
fix background connections, add manual force connection button
This commit is contained in:
@@ -35,8 +35,6 @@
|
|||||||
android:label="@string/title_activity_custom_device"
|
android:label="@string/title_activity_custom_device"
|
||||||
android:theme="@style/Theme.ALN">
|
android:theme="@style/Theme.ALN">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<!-- <action android:name="android.intent.action.MAIN" />-->
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
@@ -46,17 +44,10 @@
|
|||||||
android:theme="@style/Theme.ALN">
|
android:theme="@style/Theme.ALN">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".OldAirPodsService"
|
|
||||||
android:enabled="true"
|
|
||||||
android:exported="true"
|
|
||||||
android:foregroundServiceType="connectedDevice"
|
|
||||||
android:permission="android.permission.BLUETOOTH_CONNECT" />
|
|
||||||
<service
|
<service
|
||||||
android:name=".AirPodsService"
|
android:name=".AirPodsService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
@@ -73,6 +64,16 @@
|
|||||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
<receiver
|
||||||
|
android:name=".BootReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -10,20 +10,17 @@ import android.service.quicksettings.TileService
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
|
||||||
class AirPodsQSService: TileService() {
|
class AirPodsQSService: TileService() {
|
||||||
private val sharedPreferences = ServiceManager.getService()?.getSharedPreferences("me.kavishdevar.aln", Context.MODE_PRIVATE)
|
private val ancModes = listOf(NoiseControlMode.NOISE_CANCELLATION.name, NoiseControlMode.TRANSPARENCY.name, NoiseControlMode.ADAPTIVE.name)
|
||||||
private val offListeningModeEnabled = sharedPreferences?.getBoolean("off_listening_mode", false) == true
|
private var currentModeIndex = 2
|
||||||
private val ancModes = if (offListeningModeEnabled) listOf(NoiseControlMode.OFF.name, NoiseControlMode.NOISE_CANCELLATION.name, NoiseControlMode.TRANSPARENCY.name, NoiseControlMode.ADAPTIVE.name) else listOf(NoiseControlMode.NOISE_CANCELLATION.name, NoiseControlMode.TRANSPARENCY.name, NoiseControlMode.ADAPTIVE.name)
|
|
||||||
private var currentModeIndex = if (offListeningModeEnabled) 3 else 2
|
|
||||||
private lateinit var ancStatusReceiver: BroadcastReceiver
|
private lateinit var ancStatusReceiver: BroadcastReceiver
|
||||||
private lateinit var availabilityReceiver: BroadcastReceiver
|
private lateinit var availabilityReceiver: BroadcastReceiver
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
Log.d("AirPodsQSService", "off mode: $offListeningModeEnabled")
|
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
currentModeIndex = (ServiceManager.getService()?.getANC()?.minus(if (offListeningModeEnabled) 1 else 2)) ?: if (offListeningModeEnabled) 3 else 2
|
currentModeIndex = (ServiceManager.getService()?.getANC()?.minus(1)) ?: -1
|
||||||
if (currentModeIndex == -1) {
|
if (currentModeIndex == -1) {
|
||||||
currentModeIndex = if (offListeningModeEnabled) 3 else 2
|
currentModeIndex = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ServiceManager.getService() == null) {
|
if (ServiceManager.getService() == null) {
|
||||||
@@ -42,7 +39,7 @@ class AirPodsQSService: TileService() {
|
|||||||
ancStatusReceiver = object : BroadcastReceiver() {
|
ancStatusReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
val ancStatus = intent.getIntExtra("data", 4)
|
val ancStatus = intent.getIntExtra("data", 4)
|
||||||
currentModeIndex = ancStatus - if (offListeningModeEnabled) 1 else 2
|
currentModeIndex = ancStatus - 1
|
||||||
updateTile()
|
updateTile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,10 +86,9 @@ class AirPodsQSService: TileService() {
|
|||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
super.onClick()
|
super.onClick()
|
||||||
Log.d("QuickSettingTileService", "ANC tile clicked")
|
Log.d("QuickSettingTileService", "ANC tile clicked")
|
||||||
Log.d("QuickSettingTileService", "Current mode index: $currentModeIndex, ancModes size: ${ancModes.size}")
|
|
||||||
currentModeIndex = (currentModeIndex + 1) % ancModes.size
|
currentModeIndex = (currentModeIndex + 1) % ancModes.size
|
||||||
Log.d("QuickSettingTileService", "New mode index: $currentModeIndex")
|
Log.d("QuickSettingTileService", "New mode index: $currentModeIndex, would be set to ${currentModeIndex + 1}")
|
||||||
switchAncMode(if (offListeningModeEnabled) currentModeIndex + 1 else currentModeIndex + 2)
|
switchAncMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateTile() {
|
private fun updateTile() {
|
||||||
@@ -102,10 +98,11 @@ class AirPodsQSService: TileService() {
|
|||||||
qsTile.updateTile()
|
qsTile.updateTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun switchAncMode(modeIndex: Int) {
|
private fun switchAncMode() {
|
||||||
currentModeIndex = if (offListeningModeEnabled) modeIndex else modeIndex - 1
|
|
||||||
val airPodsService = ServiceManager.getService()
|
val airPodsService = ServiceManager.getService()
|
||||||
airPodsService?.setANCMode(if (offListeningModeEnabled) modeIndex + 1 else modeIndex)
|
Log.d("QuickSettingTileService", "Setting ANC mode to ${currentModeIndex + 2}")
|
||||||
|
airPodsService?.setANCMode(currentModeIndex + 2)
|
||||||
|
Log.d("QuickSettingTileService", "ANC mode set to ${currentModeIndex + 2}")
|
||||||
updateTile()
|
updateTile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,10 +23,21 @@ 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 org.lsposed.hiddenapibypass.HiddenApiBypass
|
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||||
|
|
||||||
|
object ServiceManager {
|
||||||
|
private var service: AirPodsService? = null
|
||||||
|
@Synchronized
|
||||||
|
fun getService(): AirPodsService? {
|
||||||
|
return service
|
||||||
|
}
|
||||||
|
@Synchronized
|
||||||
|
fun setService(service: AirPodsService?) {
|
||||||
|
this.service = service
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class AirPodsService: Service() {
|
class AirPodsService: Service() {
|
||||||
inner class LocalBinder : Binder() {
|
inner class LocalBinder : Binder() {
|
||||||
@@ -185,6 +196,7 @@ class AirPodsService: Service() {
|
|||||||
@SuppressLint("InlinedApi", "MissingPermission")
|
@SuppressLint("InlinedApi", "MissingPermission")
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
Log.d("AirPodsService", "Service started")
|
Log.d("AirPodsService", "Service started")
|
||||||
|
ServiceManager.setService(this)
|
||||||
startForegroundNotification()
|
startForegroundNotification()
|
||||||
registerReceiver(bluetoothReceiver, BluetoothReceiver.buildFilter(), RECEIVER_EXPORTED)
|
registerReceiver(bluetoothReceiver, BluetoothReceiver.buildFilter(), RECEIVER_EXPORTED)
|
||||||
|
|
||||||
@@ -223,6 +235,9 @@ class AirPodsService: Service() {
|
|||||||
val connectedDevices = proxy.connectedDevices
|
val connectedDevices = proxy.connectedDevices
|
||||||
if (connectedDevices.isNotEmpty()) {
|
if (connectedDevices.isNotEmpty()) {
|
||||||
connectToSocket(device)
|
connectToSocket(device)
|
||||||
|
this@AirPodsService.sendBroadcast(
|
||||||
|
Intent(AirPodsNotifications.AIRPODS_CONNECTED)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bluetoothAdapter.closeProfileProxy(profile, proxy)
|
bluetoothAdapter.closeProfileProxy(profile, proxy)
|
||||||
@@ -243,6 +258,12 @@ class AirPodsService: Service() {
|
|||||||
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
|
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
|
||||||
val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
|
val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
|
||||||
|
|
||||||
|
try {
|
||||||
|
socket.close()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
socket = HiddenApiBypass.newInstance(
|
socket = HiddenApiBypass.newInstance(
|
||||||
BluetoothSocket::class.java,
|
BluetoothSocket::class.java,
|
||||||
@@ -282,21 +303,17 @@ class AirPodsService: Service() {
|
|||||||
this@AirPodsService.device = device
|
this@AirPodsService.device = device
|
||||||
isConnected = true
|
isConnected = true
|
||||||
socket.let { it ->
|
socket.let { it ->
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
it.outputStream.write(Enums.HANDSHAKE.value)
|
it.outputStream.write(Enums.HANDSHAKE.value)
|
||||||
it.outputStream.flush()
|
it.outputStream.flush()
|
||||||
delay(500)
|
|
||||||
it.outputStream.write(Enums.SET_SPECIFIC_FEATURES.value)
|
it.outputStream.write(Enums.SET_SPECIFIC_FEATURES.value)
|
||||||
it.outputStream.flush()
|
it.outputStream.flush()
|
||||||
delay(500)
|
|
||||||
it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value)
|
it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value)
|
||||||
it.outputStream.flush()
|
it.outputStream.flush()
|
||||||
}
|
|
||||||
|
|
||||||
sendBroadcast(
|
sendBroadcast(
|
||||||
Intent(AirPodsNotifications.AIRPODS_CONNECTED)
|
Intent(AirPodsNotifications.AIRPODS_CONNECTED)
|
||||||
.putExtra("device", device)
|
.putExtra("device", device)
|
||||||
)
|
)
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
while (socket.isConnected == true) {
|
while (socket.isConnected == true) {
|
||||||
socket.let {
|
socket.let {
|
||||||
@@ -316,7 +333,7 @@ class AirPodsService: Service() {
|
|||||||
}
|
}
|
||||||
else if (bytesRead == -1) {
|
else if (bytesRead == -1) {
|
||||||
Log.d("AirPods Service", "Socket closed (bytesRead = -1)")
|
Log.d("AirPods Service", "Socket closed (bytesRead = -1)")
|
||||||
socket.close()
|
// socket.close()
|
||||||
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED))
|
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED))
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package me.kavishdevar.aln
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.bluetooth.BluetoothDevice
|
import android.bluetooth.BluetoothDevice
|
||||||
|
import android.bluetooth.BluetoothManager
|
||||||
|
import android.bluetooth.BluetoothProfile
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Context.MODE_PRIVATE
|
import android.content.Context.MODE_PRIVATE
|
||||||
@@ -9,6 +11,8 @@ import android.content.Intent
|
|||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.ParcelUuid
|
||||||
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
@@ -37,6 +41,7 @@ import androidx.compose.foundation.text.BasicTextField
|
|||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
||||||
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -84,6 +89,7 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.primex.core.ExperimentalToolkitApi
|
import com.primex.core.ExperimentalToolkitApi
|
||||||
import com.primex.core.blur.newBackgroundBlur
|
import com.primex.core.blur.newBackgroundBlur
|
||||||
|
import me.kavishdevar.aln.AirPodsService
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
@@ -157,33 +163,15 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) {
|
|||||||
horizontalArrangement = Arrangement.Center
|
horizontalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
if (left?.status != BatteryStatus.DISCONNECTED) {
|
if (left?.status != BatteryStatus.DISCONNECTED) {
|
||||||
Row (
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "\uDBC6\uDCE5",
|
|
||||||
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
|
||||||
)
|
|
||||||
BatteryIndicator(
|
BatteryIndicator(
|
||||||
left?.level ?: 0,
|
left?.level ?: 0,
|
||||||
left?.status == BatteryStatus.CHARGING
|
left?.status == BatteryStatus.CHARGING
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (left?.status != BatteryStatus.DISCONNECTED && right?.status != BatteryStatus.DISCONNECTED) {
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
}
|
}
|
||||||
if (right?.status != BatteryStatus.DISCONNECTED) {
|
if (right?.status != BatteryStatus.DISCONNECTED) {
|
||||||
Row (
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "\uDBC6\uDCE8",
|
|
||||||
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth(0.5f)
|
|
||||||
)
|
|
||||||
BatteryIndicator(
|
BatteryIndicator(
|
||||||
right?.level ?: 0,
|
right?.level ?: 0,
|
||||||
right?.status == BatteryStatus.CHARGING
|
right?.status == BatteryStatus.CHARGING
|
||||||
@@ -192,7 +180,6 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Column (
|
Column (
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -556,7 +543,41 @@ fun AirPodsSettingsScreen(device: BluetoothDevice?, service: AirPodsService,
|
|||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
|
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
|
||||||
containerColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color.Black.copy(0.3f) else Color(0xFFF2F2F7).copy(0.2f),
|
containerColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color.Black.copy(0.3f) else Color(0xFFF2F2F7).copy(0.2f),
|
||||||
|
),
|
||||||
|
actions = {
|
||||||
|
val context = LocalContext.current
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
val bluetoothAdapter = context.getSystemService(BluetoothManager::class.java).adapter
|
||||||
|
bluetoothAdapter.bondedDevices.forEach { device ->
|
||||||
|
if (device.uuids.contains(ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a"))) {
|
||||||
|
bluetoothAdapter.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
|
||||||
|
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
||||||
|
if (profile == BluetoothProfile.A2DP) {
|
||||||
|
val connectedDevices = proxy.connectedDevices
|
||||||
|
if (connectedDevices.isNotEmpty()) {
|
||||||
|
service.connectToSocket(device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bluetoothAdapter.closeProfileProxy(profile, proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(profile: Int) { }
|
||||||
|
}, BluetoothProfile.A2DP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = IconButtonDefaults.iconButtonColors(
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
contentColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color.White else Color.Black
|
||||||
)
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Refresh,
|
||||||
|
contentDescription = "Settings",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
@@ -1467,8 +1488,8 @@ fun BatteryIndicator(batteryPercentage: Int, charging: Boolean = false) {
|
|||||||
val batteryWidth = 40.dp
|
val batteryWidth = 40.dp
|
||||||
val batteryHeight = 15.dp
|
val batteryHeight = 15.dp
|
||||||
val batteryCornerRadius = 4.dp
|
val batteryCornerRadius = 4.dp
|
||||||
val tipWidth = 4.dp
|
val tipWidth = 5.dp
|
||||||
val tipHeight = batteryHeight * 0.3f
|
val tipHeight = batteryHeight * 0.375f
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
package me.kavishdevar.aln
|
package me.kavishdevar.aln
|
||||||
|
|
||||||
class BootReceiver {
|
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() }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -70,6 +70,22 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
try {
|
||||||
|
unbindService(serviceConnection)
|
||||||
|
Log.d("MainActivity", "Unbound service")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MainActivity", "Error while unbinding service: $e")
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
unregisterReceiver(connectionStatusReceiver)
|
||||||
|
Log.d("MainActivity", "Unregistered receiver")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("MainActivity", "Error while unregistering receiver: $e")
|
||||||
|
}
|
||||||
|
super.onStop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission", "InlinedApi")
|
@SuppressLint("MissingPermission", "InlinedApi")
|
||||||
@@ -86,7 +102,6 @@ fun Main() {
|
|||||||
|
|
||||||
val airPodsService = remember { mutableStateOf<AirPodsService?>(null) }
|
val airPodsService = remember { mutableStateOf<AirPodsService?>(null) }
|
||||||
if (permissionState.allPermissionsGranted) {
|
if (permissionState.allPermissionsGranted) {
|
||||||
Log.d("MainActivity", "HIIIIIIIIIIIIIII")
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
|
|||||||
@@ -1,463 +1,463 @@
|
|||||||
@file:Suppress("unused")
|
//@file:Suppress("unused")
|
||||||
|
//
|
||||||
package me.kavishdevar.aln
|
//package me.kavishdevar.aln
|
||||||
|
//
|
||||||
import android.annotation.SuppressLint
|
//import android.annotation.SuppressLint
|
||||||
import android.app.Notification
|
//import android.app.Notification
|
||||||
import android.app.NotificationChannel
|
//import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
//import android.app.NotificationManager
|
||||||
import android.app.Service
|
//import android.app.Service
|
||||||
import android.bluetooth.BluetoothDevice
|
//import android.bluetooth.BluetoothDevice
|
||||||
import android.bluetooth.BluetoothManager
|
//import android.bluetooth.BluetoothManager
|
||||||
import android.bluetooth.BluetoothProfile
|
//import android.bluetooth.BluetoothProfile
|
||||||
import android.bluetooth.BluetoothSocket
|
//import android.bluetooth.BluetoothSocket
|
||||||
import android.content.BroadcastReceiver
|
//import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
//import android.content.Context
|
||||||
import android.content.Intent
|
//import android.content.Intent
|
||||||
import android.content.IntentFilter
|
//import android.content.IntentFilter
|
||||||
import android.media.AudioManager
|
//import android.media.AudioManager
|
||||||
import android.os.Binder
|
//import android.os.Binder
|
||||||
import android.os.Build
|
//import android.os.Build
|
||||||
import android.os.IBinder
|
//import android.os.IBinder
|
||||||
import android.os.ParcelUuid
|
//import android.os.ParcelUuid
|
||||||
import android.util.Log
|
//import android.util.Log
|
||||||
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.launch
|
//import kotlinx.coroutines.launch
|
||||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
//import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||||
|
//
|
||||||
object ServiceManager {
|
//object ServiceManager {
|
||||||
private var service: OldAirPodsService? = null
|
// private var service: OldAirPodsService? = null
|
||||||
@Synchronized
|
// @Synchronized
|
||||||
fun getService(): OldAirPodsService? {
|
// fun getService(): OldAirPodsService? {
|
||||||
return service
|
// return service
|
||||||
}
|
// }
|
||||||
@Synchronized
|
// @Synchronized
|
||||||
fun setService(service: OldAirPodsService?) {
|
// fun setService(service: OldAirPodsService?) {
|
||||||
this.service = service
|
// this.service = service
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
class OldAirPodsService : Service() {
|
//class OldAirPodsService : Service() {
|
||||||
inner class LocalBinder : Binder() {
|
// inner class LocalBinder : Binder() {
|
||||||
fun getService(): OldAirPodsService = this@OldAirPodsService
|
// fun getService(): OldAirPodsService = this@OldAirPodsService
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onBind(intent: Intent?): IBinder {
|
// override fun onBind(intent: Intent?): IBinder {
|
||||||
return LocalBinder()
|
// return LocalBinder()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
var isConnected: Boolean = false
|
// var isConnected: Boolean = false
|
||||||
private var socket: BluetoothSocket? = null
|
// private var socket: BluetoothSocket? = null
|
||||||
|
//
|
||||||
fun sendPacket(packet: String) {
|
// fun sendPacket(packet: String) {
|
||||||
val fromHex = packet.split(" ").map { it.toInt(16).toByte() }
|
// val fromHex = packet.split(" ").map { it.toInt(16).toByte() }
|
||||||
socket?.outputStream?.write(fromHex.toByteArray())
|
// socket?.outputStream?.write(fromHex.toByteArray())
|
||||||
socket?.outputStream?.flush()
|
// socket?.outputStream?.flush()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun setANCMode(mode: Int) {
|
// fun setANCMode(mode: Int) {
|
||||||
when (mode) {
|
// when (mode) {
|
||||||
1 -> {
|
// 1 -> {
|
||||||
socket?.outputStream?.write(Enums.NOISE_CANCELLATION_OFF.value)
|
// socket?.outputStream?.write(Enums.NOISE_CANCELLATION_OFF.value)
|
||||||
}
|
// }
|
||||||
2 -> {
|
// 2 -> {
|
||||||
socket?.outputStream?.write(Enums.NOISE_CANCELLATION_ON.value)
|
// socket?.outputStream?.write(Enums.NOISE_CANCELLATION_ON.value)
|
||||||
}
|
// }
|
||||||
3 -> {
|
// 3 -> {
|
||||||
socket?.outputStream?.write(Enums.NOISE_CANCELLATION_TRANSPARENCY.value)
|
// socket?.outputStream?.write(Enums.NOISE_CANCELLATION_TRANSPARENCY.value)
|
||||||
}
|
// }
|
||||||
4 -> {
|
// 4 -> {
|
||||||
socket?.outputStream?.write(Enums.NOISE_CANCELLATION_ADAPTIVE.value)
|
// socket?.outputStream?.write(Enums.NOISE_CANCELLATION_ADAPTIVE.value)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
socket?.outputStream?.flush()
|
// socket?.outputStream?.flush()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun setCAEnabled(enabled: Boolean) {
|
// fun setCAEnabled(enabled: Boolean) {
|
||||||
socket?.outputStream?.write(if (enabled) Enums.SET_CONVERSATION_AWARENESS_ON.value else Enums.SET_CONVERSATION_AWARENESS_OFF.value)
|
// socket?.outputStream?.write(if (enabled) Enums.SET_CONVERSATION_AWARENESS_ON.value else Enums.SET_CONVERSATION_AWARENESS_OFF.value)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun setOffListeningMode(enabled: Boolean) {
|
// fun setOffListeningMode(enabled: Boolean) {
|
||||||
socket?.outputStream?.write(byteArrayOf(0x04, 0x00 ,0x04, 0x00, 0x09, 0x00, 0x34, if (enabled) 0x01 else 0x02, 0x00, 0x00, 0x00))
|
// socket?.outputStream?.write(byteArrayOf(0x04, 0x00 ,0x04, 0x00, 0x09, 0x00, 0x34, if (enabled) 0x01 else 0x02, 0x00, 0x00, 0x00))
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun setAdaptiveStrength(strength: Int) {
|
// fun setAdaptiveStrength(strength: Int) {
|
||||||
val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x2E, strength.toByte(), 0x00, 0x00, 0x00)
|
// val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x2E, strength.toByte(), 0x00, 0x00, 0x00)
|
||||||
socket?.outputStream?.write(bytes)
|
// socket?.outputStream?.write(bytes)
|
||||||
socket?.outputStream?.flush()
|
// socket?.outputStream?.flush()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun setPressSpeed(speed: Int) {
|
// fun setPressSpeed(speed: Int) {
|
||||||
val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x17, speed.toByte(), 0x00, 0x00, 0x00)
|
// val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x17, speed.toByte(), 0x00, 0x00, 0x00)
|
||||||
socket?.outputStream?.write(bytes)
|
// socket?.outputStream?.write(bytes)
|
||||||
socket?.outputStream?.flush()
|
// socket?.outputStream?.flush()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun setPressAndHoldDuration(speed: Int) {
|
// fun setPressAndHoldDuration(speed: Int) {
|
||||||
val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x18, speed.toByte(), 0x00, 0x00, 0x00)
|
// val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x18, speed.toByte(), 0x00, 0x00, 0x00)
|
||||||
socket?.outputStream?.write(bytes)
|
// socket?.outputStream?.write(bytes)
|
||||||
socket?.outputStream?.flush()
|
// socket?.outputStream?.flush()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun setNoiseCancellationWithOnePod(enabled: Boolean) {
|
// fun setNoiseCancellationWithOnePod(enabled: Boolean) {
|
||||||
val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1B, if (enabled) 0x01 else 0x02, 0x00, 0x00, 0x00)
|
// val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1B, if (enabled) 0x01 else 0x02, 0x00, 0x00, 0x00)
|
||||||
socket?.outputStream?.write(bytes)
|
// socket?.outputStream?.write(bytes)
|
||||||
socket?.outputStream?.flush()
|
// socket?.outputStream?.flush()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun setVolumeControl(enabled: Boolean) {
|
// fun setVolumeControl(enabled: Boolean) {
|
||||||
val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x25, if (enabled) 0x01 else 0x02, 0x00, 0x00, 0x00)
|
// val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x25, if (enabled) 0x01 else 0x02, 0x00, 0x00, 0x00)
|
||||||
socket?.outputStream?.write(bytes)
|
// socket?.outputStream?.write(bytes)
|
||||||
socket?.outputStream?.flush()
|
// socket?.outputStream?.flush()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun setVolumeSwipeSpeed(speed: Int) {
|
// fun setVolumeSwipeSpeed(speed: Int) {
|
||||||
val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x23, speed.toByte(), 0x00, 0x00, 0x00)
|
// val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x23, speed.toByte(), 0x00, 0x00, 0x00)
|
||||||
socket?.outputStream?.write(bytes)
|
// socket?.outputStream?.write(bytes)
|
||||||
socket?.outputStream?.flush()
|
// socket?.outputStream?.flush()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun setToneVolume(volume: Int) {
|
// fun setToneVolume(volume: Int) {
|
||||||
val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1F, volume.toByte(), 0x50, 0x00, 0x00)
|
// val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1F, volume.toByte(), 0x50, 0x00, 0x00)
|
||||||
socket?.outputStream?.write(bytes)
|
// socket?.outputStream?.write(bytes)
|
||||||
socket?.outputStream?.flush()
|
// socket?.outputStream?.flush()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
val earDetectionNotification = AirPodsNotifications.EarDetection()
|
// val earDetectionNotification = AirPodsNotifications.EarDetection()
|
||||||
val ancNotification = AirPodsNotifications.ANC()
|
// val ancNotification = AirPodsNotifications.ANC()
|
||||||
val batteryNotification = AirPodsNotifications.BatteryNotification()
|
// val batteryNotification = AirPodsNotifications.BatteryNotification()
|
||||||
val conversationAwarenessNotification = AirPodsNotifications.ConversationalAwarenessNotification()
|
// val conversationAwarenessNotification = AirPodsNotifications.ConversationalAwarenessNotification()
|
||||||
|
//
|
||||||
var earDetectionEnabled = true
|
// var earDetectionEnabled = true
|
||||||
|
//
|
||||||
fun setCaseChargingSounds(enabled: Boolean) {
|
// fun setCaseChargingSounds(enabled: Boolean) {
|
||||||
val bytes = byteArrayOf(0x12, 0x3a, 0x00, 0x01, 0x00, 0x08, if (enabled) 0x00 else 0x01)
|
// val bytes = byteArrayOf(0x12, 0x3a, 0x00, 0x01, 0x00, 0x08, if (enabled) 0x00 else 0x01)
|
||||||
socket?.outputStream?.write(bytes)
|
// socket?.outputStream?.write(bytes)
|
||||||
socket?.outputStream?.flush()
|
// socket?.outputStream?.flush()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun setEarDetection(enabled: Boolean) {
|
// fun setEarDetection(enabled: Boolean) {
|
||||||
earDetectionEnabled = enabled
|
// earDetectionEnabled = enabled
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun getBattery(): List<Battery> {
|
// fun getBattery(): List<Battery> {
|
||||||
return batteryNotification.getBattery()
|
// return batteryNotification.getBattery()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun getANC(): Int {
|
// fun getANC(): Int {
|
||||||
return ancNotification.status
|
// return ancNotification.status
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
private fun createNotification(): Notification {
|
// private fun createNotification(): Notification {
|
||||||
val channelId = "battery"
|
// val channelId = "battery"
|
||||||
val notificationBuilder = NotificationCompat.Builder(this, channelId)
|
// val notificationBuilder = NotificationCompat.Builder(this, channelId)
|
||||||
.setSmallIcon(R.drawable.pro_2_buds)
|
// .setSmallIcon(R.drawable.pro_2_buds)
|
||||||
.setContentTitle("AirPods Connected")
|
// .setContentTitle("AirPods Connected")
|
||||||
.setOngoing(true)
|
// .setOngoing(true)
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
// .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
|
//
|
||||||
val channel =
|
// val channel =
|
||||||
NotificationChannel(channelId, "Battery Notification", NotificationManager.IMPORTANCE_LOW)
|
// NotificationChannel(channelId, "Battery Notification", NotificationManager.IMPORTANCE_LOW)
|
||||||
|
//
|
||||||
val notificationManager = getSystemService(NotificationManager::class.java)
|
// val notificationManager = getSystemService(NotificationManager::class.java)
|
||||||
notificationManager.createNotificationChannel(channel)
|
// notificationManager.createNotificationChannel(channel)
|
||||||
return notificationBuilder.build()
|
// return notificationBuilder.build()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun disconnectAudio(context: Context, device: BluetoothDevice?) {
|
// fun disconnectAudio(context: Context, device: BluetoothDevice?) {
|
||||||
val bluetoothAdapter = context.getSystemService(BluetoothManager::class.java).adapter
|
// val bluetoothAdapter = context.getSystemService(BluetoothManager::class.java).adapter
|
||||||
|
//
|
||||||
bluetoothAdapter?.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
|
// bluetoothAdapter?.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
|
||||||
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
// override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
||||||
if (profile == BluetoothProfile.A2DP) {
|
// if (profile == BluetoothProfile.A2DP) {
|
||||||
try {
|
// try {
|
||||||
val method = proxy.javaClass.getMethod("disconnect", BluetoothDevice::class.java)
|
// val method = proxy.javaClass.getMethod("disconnect", BluetoothDevice::class.java)
|
||||||
method.invoke(proxy, device)
|
// method.invoke(proxy, device)
|
||||||
} catch (e: Exception) {
|
// } catch (e: Exception) {
|
||||||
e.printStackTrace()
|
// e.printStackTrace()
|
||||||
} finally {
|
// } finally {
|
||||||
bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, proxy)
|
// bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, proxy)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onServiceDisconnected(profile: Int) { }
|
// override fun onServiceDisconnected(profile: Int) { }
|
||||||
}, BluetoothProfile.A2DP)
|
// }, BluetoothProfile.A2DP)
|
||||||
|
//
|
||||||
bluetoothAdapter?.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
|
// bluetoothAdapter?.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
|
||||||
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
// override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
||||||
if (profile == BluetoothProfile.HEADSET) {
|
// if (profile == BluetoothProfile.HEADSET) {
|
||||||
try {
|
// try {
|
||||||
val method = proxy.javaClass.getMethod("disconnect", BluetoothDevice::class.java)
|
// val method = proxy.javaClass.getMethod("disconnect", BluetoothDevice::class.java)
|
||||||
method.invoke(proxy, device)
|
// method.invoke(proxy, device)
|
||||||
} catch (e: Exception) {
|
// } catch (e: Exception) {
|
||||||
e.printStackTrace()
|
// e.printStackTrace()
|
||||||
} finally {
|
// } finally {
|
||||||
bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, proxy)
|
// bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, proxy)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onServiceDisconnected(profile: Int) { }
|
// override fun onServiceDisconnected(profile: Int) { }
|
||||||
}, BluetoothProfile.HEADSET)
|
// }, BluetoothProfile.HEADSET)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun connectAudio(context: Context, device: BluetoothDevice?) {
|
// fun connectAudio(context: Context, device: BluetoothDevice?) {
|
||||||
val bluetoothAdapter = context.getSystemService(BluetoothManager::class.java).adapter
|
// val bluetoothAdapter = context.getSystemService(BluetoothManager::class.java).adapter
|
||||||
|
//
|
||||||
bluetoothAdapter?.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
|
// bluetoothAdapter?.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
|
||||||
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
// override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
||||||
if (profile == BluetoothProfile.A2DP) {
|
// if (profile == BluetoothProfile.A2DP) {
|
||||||
try {
|
// try {
|
||||||
val method = proxy.javaClass.getMethod("connect", BluetoothDevice::class.java)
|
// val method = proxy.javaClass.getMethod("connect", BluetoothDevice::class.java)
|
||||||
method.invoke(proxy, device)
|
// method.invoke(proxy, device)
|
||||||
} catch (e: Exception) {
|
// } catch (e: Exception) {
|
||||||
e.printStackTrace()
|
// e.printStackTrace()
|
||||||
} finally {
|
// } finally {
|
||||||
bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, proxy)
|
// bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, proxy)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onServiceDisconnected(profile: Int) { }
|
// override fun onServiceDisconnected(profile: Int) { }
|
||||||
}, BluetoothProfile.A2DP)
|
// }, BluetoothProfile.A2DP)
|
||||||
|
//
|
||||||
bluetoothAdapter?.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
|
// bluetoothAdapter?.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
|
||||||
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
// override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
||||||
if (profile == BluetoothProfile.HEADSET) {
|
// if (profile == BluetoothProfile.HEADSET) {
|
||||||
try {
|
// try {
|
||||||
val method = proxy.javaClass.getMethod("connect", BluetoothDevice::class.java)
|
// val method = proxy.javaClass.getMethod("connect", BluetoothDevice::class.java)
|
||||||
method.invoke(proxy, device)
|
// method.invoke(proxy, device)
|
||||||
} catch (e: Exception) {
|
// } catch (e: Exception) {
|
||||||
e.printStackTrace()
|
// e.printStackTrace()
|
||||||
} finally {
|
// } finally {
|
||||||
bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, proxy)
|
// bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, proxy)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onServiceDisconnected(profile: Int) { }
|
// override fun onServiceDisconnected(profile: Int) { }
|
||||||
}, BluetoothProfile.HEADSET)
|
// }, BluetoothProfile.HEADSET)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun setName(name: String) {
|
// fun setName(name: String) {
|
||||||
val nameBytes = name.toByteArray()
|
// val nameBytes = name.toByteArray()
|
||||||
val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x1a, 0x00, 0x01,
|
// val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x1a, 0x00, 0x01,
|
||||||
nameBytes.size.toByte(), 0x00) + nameBytes
|
// nameBytes.size.toByte(), 0x00) + nameBytes
|
||||||
socket?.outputStream?.write(bytes)
|
// socket?.outputStream?.write(bytes)
|
||||||
socket?.outputStream?.flush()
|
// socket?.outputStream?.flush()
|
||||||
val hex = bytes.joinToString(" ") { "%02X".format(it) }
|
// val hex = bytes.joinToString(" ") { "%02X".format(it) }
|
||||||
Log.d("OldAirPodsService", "setName: $name, sent packet: $hex")
|
// Log.d("OldAirPodsService", "setName: $name, sent packet: $hex")
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@SuppressLint("MissingPermission", "InlinedApi")
|
// @SuppressLint("MissingPermission", "InlinedApi")
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
// override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
//
|
||||||
val notification = createNotification()
|
// val notification = createNotification()
|
||||||
startForeground(1, notification)
|
// startForeground(1, notification)
|
||||||
|
//
|
||||||
ServiceManager.setService(this)
|
// ServiceManager.setService(this)
|
||||||
|
//
|
||||||
if (isConnected) {
|
// if (isConnected) {
|
||||||
return START_STICKY
|
// return START_STICKY
|
||||||
}
|
// }
|
||||||
isConnected = true
|
// isConnected = true
|
||||||
|
//
|
||||||
@Suppress("DEPRECATION") val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) intent?.getParcelableExtra("device", BluetoothDevice::class.java) else intent?.getParcelableExtra("device")
|
// @Suppress("DEPRECATION") val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) intent?.getParcelableExtra("device", BluetoothDevice::class.java) else intent?.getParcelableExtra("device")
|
||||||
|
//
|
||||||
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
|
// HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
|
||||||
val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
|
// val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
|
||||||
|
//
|
||||||
socket = HiddenApiBypass.newInstance(BluetoothSocket::class.java, 3, true, true, device, 0x1001, uuid) as BluetoothSocket?
|
// socket = HiddenApiBypass.newInstance(BluetoothSocket::class.java, 3, true, true, device, 0x1001, uuid) as BluetoothSocket?
|
||||||
try {
|
// try {
|
||||||
socket?.connect()
|
// socket?.connect()
|
||||||
socket?.let { it ->
|
// socket?.let { it ->
|
||||||
it.outputStream.write(Enums.HANDSHAKE.value)
|
// it.outputStream.write(Enums.HANDSHAKE.value)
|
||||||
it.outputStream.write(Enums.SET_SPECIFIC_FEATURES.value)
|
// it.outputStream.write(Enums.SET_SPECIFIC_FEATURES.value)
|
||||||
it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value)
|
// it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value)
|
||||||
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_CONNECTED))
|
// sendBroadcast(Intent(AirPodsNotifications.AIRPODS_CONNECTED))
|
||||||
it.outputStream.flush()
|
// it.outputStream.flush()
|
||||||
|
//
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
// CoroutineScope(Dispatchers.IO).launch {
|
||||||
while (socket?.isConnected == true) {
|
// while (socket?.isConnected == true) {
|
||||||
socket?.let {
|
// socket?.let {
|
||||||
val audioManager = this@OldAirPodsService.getSystemService(AUDIO_SERVICE) as AudioManager
|
// val audioManager = this@OldAirPodsService.getSystemService(AUDIO_SERVICE) as AudioManager
|
||||||
MediaController.initialize(audioManager)
|
// MediaController.initialize(audioManager)
|
||||||
val buffer = ByteArray(1024)
|
// val buffer = ByteArray(1024)
|
||||||
val bytesRead = it.inputStream.read(buffer)
|
// val bytesRead = it.inputStream.read(buffer)
|
||||||
var data: ByteArray = byteArrayOf()
|
// var data: ByteArray = byteArrayOf()
|
||||||
if (bytesRead > 0) {
|
// if (bytesRead > 0) {
|
||||||
data = buffer.copyOfRange(0, bytesRead)
|
// data = buffer.copyOfRange(0, bytesRead)
|
||||||
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DATA).apply {
|
// sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DATA).apply {
|
||||||
putExtra("data", buffer.copyOfRange(0, bytesRead))
|
// putExtra("data", buffer.copyOfRange(0, bytesRead))
|
||||||
})
|
// })
|
||||||
val bytes = buffer.copyOfRange(0, bytesRead)
|
// val bytes = buffer.copyOfRange(0, bytesRead)
|
||||||
val formattedHex = bytes.joinToString(" ") { "%02X".format(it) }
|
// val formattedHex = bytes.joinToString(" ") { "%02X".format(it) }
|
||||||
Log.d("AirPods Data", "Data received: $formattedHex")
|
// Log.d("AirPods Data", "Data received: $formattedHex")
|
||||||
}
|
// }
|
||||||
else if (bytesRead == -1) {
|
// else if (bytesRead == -1) {
|
||||||
Log.d("AirPods Service", "Socket closed (bytesRead = -1)")
|
// Log.d("AirPods Service", "Socket closed (bytesRead = -1)")
|
||||||
this@OldAirPodsService.stopForeground(STOP_FOREGROUND_REMOVE)
|
// this@OldAirPodsService.stopForeground(STOP_FOREGROUND_REMOVE)
|
||||||
socket?.close()
|
// socket?.close()
|
||||||
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED))
|
// sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED))
|
||||||
return@launch
|
// return@launch
|
||||||
}
|
// }
|
||||||
var inEar = false
|
// var inEar = false
|
||||||
var inEarData = listOf<Boolean>()
|
// var inEarData = listOf<Boolean>()
|
||||||
if (earDetectionNotification.isEarDetectionData(data)) {
|
// if (earDetectionNotification.isEarDetectionData(data)) {
|
||||||
earDetectionNotification.setStatus(data)
|
// earDetectionNotification.setStatus(data)
|
||||||
sendBroadcast(Intent(AirPodsNotifications.EAR_DETECTION_DATA).apply {
|
// sendBroadcast(Intent(AirPodsNotifications.EAR_DETECTION_DATA).apply {
|
||||||
val list = earDetectionNotification.status
|
// val list = earDetectionNotification.status
|
||||||
val bytes = ByteArray(2)
|
// val bytes = ByteArray(2)
|
||||||
bytes[0] = list[0]
|
// bytes[0] = list[0]
|
||||||
bytes[1] = list[1]
|
// bytes[1] = list[1]
|
||||||
putExtra("data", bytes)
|
// putExtra("data", bytes)
|
||||||
})
|
// })
|
||||||
Log.d("AirPods Parser", "Ear Detection: ${earDetectionNotification.status[0]} ${earDetectionNotification.status[1]}")
|
// Log.d("AirPods Parser", "Ear Detection: ${earDetectionNotification.status[0]} ${earDetectionNotification.status[1]}")
|
||||||
var justEnabledA2dp = false
|
// var justEnabledA2dp = false
|
||||||
val earReceiver = object : BroadcastReceiver() {
|
// val earReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
// override fun onReceive(context: Context, intent: Intent) {
|
||||||
val data = intent.getByteArrayExtra("data")
|
// val data = intent.getByteArrayExtra("data")
|
||||||
if (data != null && earDetectionEnabled) {
|
// if (data != null && earDetectionEnabled) {
|
||||||
inEar = if (data.find { it == 0x02.toByte() } != null || data.find { it == 0x03.toByte() } != null) {
|
// inEar = if (data.find { it == 0x02.toByte() } != null || data.find { it == 0x03.toByte() } != null) {
|
||||||
data[0] == 0x00.toByte() || data[1] == 0x00.toByte()
|
// data[0] == 0x00.toByte() || data[1] == 0x00.toByte()
|
||||||
} else {
|
// } else {
|
||||||
data[0] == 0x00.toByte() && data[1] == 0x00.toByte()
|
// data[0] == 0x00.toByte() && data[1] == 0x00.toByte()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
val newInEarData = listOf(data[0] == 0x00.toByte(), data[1] == 0x00.toByte())
|
// val newInEarData = listOf(data[0] == 0x00.toByte(), data[1] == 0x00.toByte())
|
||||||
if (newInEarData.contains(true) && inEarData == listOf(false, false)) {
|
// if (newInEarData.contains(true) && inEarData == listOf(false, false)) {
|
||||||
connectAudio(this@OldAirPodsService, device)
|
// connectAudio(this@OldAirPodsService, device)
|
||||||
justEnabledA2dp = true
|
// justEnabledA2dp = true
|
||||||
val bluetoothAdapter = this@OldAirPodsService.getSystemService(BluetoothManager::class.java).adapter
|
// val bluetoothAdapter = this@OldAirPodsService.getSystemService(BluetoothManager::class.java).adapter
|
||||||
bluetoothAdapter.getProfileProxy(
|
// bluetoothAdapter.getProfileProxy(
|
||||||
this@OldAirPodsService, object : BluetoothProfile.ServiceListener {
|
// this@OldAirPodsService, object : BluetoothProfile.ServiceListener {
|
||||||
override fun onServiceConnected(
|
// override fun onServiceConnected(
|
||||||
profile: Int,
|
// profile: Int,
|
||||||
proxy: BluetoothProfile
|
// proxy: BluetoothProfile
|
||||||
) {
|
// ) {
|
||||||
if (profile == BluetoothProfile.A2DP) {
|
// if (profile == BluetoothProfile.A2DP) {
|
||||||
val connectedDevices =
|
// val connectedDevices =
|
||||||
proxy.connectedDevices
|
// proxy.connectedDevices
|
||||||
if (connectedDevices.isNotEmpty()) {
|
// if (connectedDevices.isNotEmpty()) {
|
||||||
MediaController.sendPlay()
|
// MediaController.sendPlay()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
bluetoothAdapter.closeProfileProxy(
|
// bluetoothAdapter.closeProfileProxy(
|
||||||
profile,
|
// profile,
|
||||||
proxy
|
// proxy
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onServiceDisconnected(
|
// override fun onServiceDisconnected(
|
||||||
profile: Int
|
// profile: Int
|
||||||
) {
|
// ) {
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
,BluetoothProfile.A2DP
|
// ,BluetoothProfile.A2DP
|
||||||
)
|
// )
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
else if (newInEarData == listOf(false, false)){
|
// else if (newInEarData == listOf(false, false)){
|
||||||
disconnectAudio(this@OldAirPodsService, device)
|
// disconnectAudio(this@OldAirPodsService, device)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
inEarData = newInEarData
|
// inEarData = newInEarData
|
||||||
|
//
|
||||||
if (inEar == true) {
|
// if (inEar == true) {
|
||||||
if (!justEnabledA2dp) {
|
// if (!justEnabledA2dp) {
|
||||||
justEnabledA2dp = false
|
// justEnabledA2dp = false
|
||||||
MediaController.sendPlay()
|
// MediaController.sendPlay()
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
MediaController.sendPause()
|
// MediaController.sendPause()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
val earIntentFilter = IntentFilter(AirPodsNotifications.EAR_DETECTION_DATA)
|
// val earIntentFilter = IntentFilter(AirPodsNotifications.EAR_DETECTION_DATA)
|
||||||
this@OldAirPodsService.registerReceiver(earReceiver, earIntentFilter,
|
// this@OldAirPodsService.registerReceiver(earReceiver, earIntentFilter,
|
||||||
RECEIVER_EXPORTED
|
// RECEIVER_EXPORTED
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
else if (ancNotification.isANCData(data)) {
|
// else if (ancNotification.isANCData(data)) {
|
||||||
ancNotification.setStatus(data)
|
// ancNotification.setStatus(data)
|
||||||
sendBroadcast(Intent(AirPodsNotifications.ANC_DATA).apply {
|
// sendBroadcast(Intent(AirPodsNotifications.ANC_DATA).apply {
|
||||||
putExtra("data", ancNotification.status)
|
// putExtra("data", ancNotification.status)
|
||||||
})
|
// })
|
||||||
Log.d("AirPods Parser", "ANC: ${ancNotification.status}")
|
// Log.d("AirPods Parser", "ANC: ${ancNotification.status}")
|
||||||
}
|
// }
|
||||||
else if (batteryNotification.isBatteryData(data)) {
|
// else if (batteryNotification.isBatteryData(data)) {
|
||||||
batteryNotification.setBattery(data)
|
// batteryNotification.setBattery(data)
|
||||||
sendBroadcast(Intent(AirPodsNotifications.BATTERY_DATA).apply {
|
// sendBroadcast(Intent(AirPodsNotifications.BATTERY_DATA).apply {
|
||||||
putParcelableArrayListExtra("data", ArrayList(batteryNotification.getBattery()))
|
// putParcelableArrayListExtra("data", ArrayList(batteryNotification.getBattery()))
|
||||||
})
|
// })
|
||||||
for (battery in batteryNotification.getBattery()) {
|
// for (battery in batteryNotification.getBattery()) {
|
||||||
Log.d("AirPods Parser", "${battery.getComponentName()}: ${battery.getStatusName()} at ${battery.level}% ")
|
// Log.d("AirPods Parser", "${battery.getComponentName()}: ${battery.getStatusName()} at ${battery.level}% ")
|
||||||
}
|
// }
|
||||||
// if both are charging, disconnect audio profiles
|
//// if both are charging, disconnect audio profiles
|
||||||
if (batteryNotification.getBattery()[0].status == 1 && batteryNotification.getBattery()[1].status == 1) {
|
// if (batteryNotification.getBattery()[0].status == 1 && batteryNotification.getBattery()[1].status == 1) {
|
||||||
disconnectAudio(this@OldAirPodsService, device)
|
// disconnectAudio(this@OldAirPodsService, device)
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
connectAudio(this@OldAirPodsService, device)
|
// connectAudio(this@OldAirPodsService, device)
|
||||||
}
|
// }
|
||||||
// updatePodsStatus(device!!, batteryNotification.getBattery())
|
//// updatePodsStatus(device!!, batteryNotification.getBattery())
|
||||||
}
|
// }
|
||||||
else if (conversationAwarenessNotification.isConversationalAwarenessData(data)) {
|
// else if (conversationAwarenessNotification.isConversationalAwarenessData(data)) {
|
||||||
conversationAwarenessNotification.setData(data)
|
// conversationAwarenessNotification.setData(data)
|
||||||
sendBroadcast(Intent(AirPodsNotifications.CA_DATA).apply {
|
// sendBroadcast(Intent(AirPodsNotifications.CA_DATA).apply {
|
||||||
putExtra("data", conversationAwarenessNotification.status)
|
// putExtra("data", conversationAwarenessNotification.status)
|
||||||
})
|
// })
|
||||||
|
//
|
||||||
|
//
|
||||||
if (conversationAwarenessNotification.status == 1.toByte() || conversationAwarenessNotification.status == 2.toByte()) {
|
// if (conversationAwarenessNotification.status == 1.toByte() || conversationAwarenessNotification.status == 2.toByte()) {
|
||||||
MediaController.startSpeaking()
|
// MediaController.startSpeaking()
|
||||||
} else if (conversationAwarenessNotification.status == 8.toByte() || conversationAwarenessNotification.status == 9.toByte()) {
|
// } else if (conversationAwarenessNotification.status == 8.toByte() || conversationAwarenessNotification.status == 9.toByte()) {
|
||||||
MediaController.stopSpeaking()
|
// MediaController.stopSpeaking()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Log.d("AirPods Parser", "Conversation Awareness: ${conversationAwarenessNotification.status}")
|
// Log.d("AirPods Parser", "Conversation Awareness: ${conversationAwarenessNotification.status}")
|
||||||
}
|
// }
|
||||||
else { }
|
// else { }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Log.d("AirPods Service", "Socket closed")
|
// Log.d("AirPods Service", "Socket closed")
|
||||||
isConnected = false
|
// isConnected = false
|
||||||
this@OldAirPodsService.stopForeground(STOP_FOREGROUND_REMOVE)
|
// this@OldAirPodsService.stopForeground(STOP_FOREGROUND_REMOVE)
|
||||||
socket?.close()
|
// socket?.close()
|
||||||
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED))
|
// sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
catch (e: Exception) {
|
// catch (e: Exception) {
|
||||||
Log.e("AirPodsSettingsScreen", "Error connecting to device: ${e.message}")
|
// Log.e("AirPodsSettingsScreen", "Error connecting to device: ${e.message}")
|
||||||
}
|
// }
|
||||||
return START_STICKY
|
// return START_STICKY
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onDestroy() {
|
// override fun onDestroy() {
|
||||||
super.onDestroy()
|
// super.onDestroy()
|
||||||
socket?.close()
|
// socket?.close()
|
||||||
isConnected = false
|
// isConnected = false
|
||||||
ServiceManager.setService(null)
|
// ServiceManager.setService(null)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun setPVEnabled(enabled: Boolean) {
|
// fun setPVEnabled(enabled: Boolean) {
|
||||||
var hex = "04 00 04 00 09 00 26 ${if (enabled) "01" else "02"} 00 00 00"
|
// var hex = "04 00 04 00 09 00 26 ${if (enabled) "01" else "02"} 00 00 00"
|
||||||
var bytes = hex.split(" ").map { it.toInt(16).toByte() }.toByteArray()
|
// var bytes = hex.split(" ").map { it.toInt(16).toByte() }.toByteArray()
|
||||||
socket?.outputStream?.write(bytes)
|
// socket?.outputStream?.write(bytes)
|
||||||
hex = "04 00 04 00 17 00 00 00 10 00 12 00 08 E${if (enabled) "6" else "5"} 05 10 02 42 0B 08 50 10 02 1A 05 02 ${if (enabled) "32" else "00"} 00 00 00"
|
// hex = "04 00 04 00 17 00 00 00 10 00 12 00 08 E${if (enabled) "6" else "5"} 05 10 02 42 0B 08 50 10 02 1A 05 02 ${if (enabled) "32" else "00"} 00 00 00"
|
||||||
bytes = hex.split(" ").map { it.toInt(16).toByte() }.toByteArray()
|
// bytes = hex.split(" ").map { it.toInt(16).toByte() }.toByteArray()
|
||||||
socket?.outputStream?.write(bytes)
|
// socket?.outputStream?.write(bytes)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
fun setLoudSoundReduction(enabled: Boolean) {
|
// fun setLoudSoundReduction(enabled: Boolean) {
|
||||||
val hex = "52 1B 00 0${if (enabled) "1" else "0"}"
|
// val hex = "52 1B 00 0${if (enabled) "1" else "0"}"
|
||||||
val bytes = hex.split(" ").map { it.toInt(16).toByte() }.toByteArray()
|
// val bytes = hex.split(" ").map { it.toInt(16).toByte() }.toByteArray()
|
||||||
socket?.outputStream?.write(bytes)
|
// socket?.outputStream?.write(bytes)
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">ALN</string>
|
<string name="app_name">ALN</string>
|
||||||
<string name="title_activity_debug">DebugActivity</string>
|
<string name="title_activity_custom_device">GATT Testing</string>
|
||||||
<string name="title_activity_custom_device">CustomDevice</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user