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