@@ -50,6 +50,8 @@ import android.os.Handler
5050import android.os.IBinder
5151import android.os.Looper
5252import android.os.ParcelUuid
53+ import android.telephony.PhoneStateListener
54+ import android.telephony.TelephonyManager
5355import android.util.Log
5456import android.util.TypedValue
5557import android.view.View
@@ -76,9 +78,10 @@ import me.kavishdevar.aln.utils.BatteryStatus
7678import me.kavishdevar.aln.utils.CrossDevice
7779import me.kavishdevar.aln.utils.CrossDevicePackets
7880import me.kavishdevar.aln.utils.Enums
81+ import me.kavishdevar.aln.utils.IslandWindow
7982import me.kavishdevar.aln.utils.LongPressPackets
8083import me.kavishdevar.aln.utils.MediaController
81- import me.kavishdevar.aln.utils.Window
84+ import me.kavishdevar.aln.utils.PopupWindow
8285import me.kavishdevar.aln.widgets.BatteryWidget
8386import me.kavishdevar.aln.widgets.NoiseControlWidget
8487import org.lsposed.hiddenapibypass.HiddenApiBypass
@@ -114,7 +117,7 @@ object ServiceManager {
114117
115118// @Suppress("unused")
116119class AirPodsService : Service () {
117- private var macAddress = " "
120+ var macAddress = " "
118121
119122 inner class LocalBinder : Binder () {
120123 fun getService (): AirPodsService = this @AirPodsService
@@ -126,6 +129,9 @@ class AirPodsService : Service() {
126129 private val _packetLogsFlow = MutableStateFlow <Set <String >>(emptySet())
127130 val packetLogsFlow: StateFlow <Set <String >> get() = _packetLogsFlow
128131
132+ private lateinit var telephonyManager: TelephonyManager
133+ private lateinit var phoneStateListener: PhoneStateListener
134+
129135 override fun onCreate () {
130136 super .onCreate()
131137 sharedPreferencesLogs = getSharedPreferences(" packet_logs" , MODE_PRIVATE )
@@ -166,10 +172,25 @@ class AirPodsService : Service() {
166172 if (popupShown) {
167173 return
168174 }
169- val window = Window (service.applicationContext)
170- window .open(name, batteryNotification)
175+ val popupWindow = PopupWindow (service.applicationContext)
176+ popupWindow .open(name, batteryNotification)
171177 popupShown = true
172178 }
179+ var islandOpen = false
180+ var islandWindow: IslandWindow ? = null
181+ @SuppressLint(" MissingPermission" )
182+ fun showIsland (service : Service , batteryPercentage : Int , takingOver : Boolean = false) {
183+ Log .d(" AirPodsService" , " Showing island window" )
184+ islandWindow = IslandWindow (service.applicationContext)
185+ islandWindow!! .show(sharedPreferences.getString(" name" , " AirPods Pro" ).toString(), batteryPercentage, this , takingOver)
186+ }
187+
188+ @OptIn(ExperimentalMaterial3Api ::class )
189+ fun startMainActivity () {
190+ val intent = Intent (this , MainActivity ::class .java)
191+ intent.addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
192+ startActivity(intent)
193+ }
173194
174195 @Suppress(" ClassName" )
175196 private object bluetoothReceiver : BroadcastReceiver() {
@@ -220,23 +241,7 @@ class AirPodsService : Service() {
220241 object BatteryChangedIntentReceiver : BroadcastReceiver() {
221242 override fun onReceive (context : Context ? , intent : Intent ) {
222243 if (intent.action == Intent .ACTION_BATTERY_CHANGED ) {
223- val level = intent.getIntExtra(" level" , 0 )
224- val scale = intent.getIntExtra(" scale" , 100 )
225- val batteryPct = level * 100 / scale
226- val charging = intent.getIntExtra(
227- BatteryManager .EXTRA_STATUS ,
228- - 1
229- ) == BatteryManager .BATTERY_STATUS_CHARGING
230- if (ServiceManager .getService()?.widgetMobileBatteryEnabled == true ) {
231- val appWidgetManager = AppWidgetManager .getInstance(context)
232- val componentName = ComponentName (context!! , BatteryWidget ::class .java)
233- val widgetIds = appWidgetManager.getAppWidgetIds(componentName)
234- val remoteViews = RemoteViews (context.packageName, R .layout.battery_widget)
235- remoteViews.setTextViewText(R .id.phone_battery_widget, " $batteryPct %" )
236- remoteViews.setProgressBar(R .id.phone_battery_progress, 100 , batteryPct, false )
237-
238- appWidgetManager.updateAppWidget(widgetIds, remoteViews)
239- }
244+ ServiceManager .getService()?.updateBatteryWidget()
240245 } else if (intent.action == AirPodsNotifications .DISCONNECT_RECEIVERS ) {
241246 try {
242247 context?.unregisterReceiver(this )
@@ -568,13 +573,55 @@ class AirPodsService : Service() {
568573 Log .d(" AirPodsService" , " Service started" )
569574 ServiceManager .setService(this )
570575 startForegroundNotification()
571-
576+ val audioManager =
577+ this @AirPodsService.getSystemService(AUDIO_SERVICE ) as AudioManager
578+ MediaController .initialize(
579+ audioManager,
580+ this @AirPodsService.getSharedPreferences(
581+ " settings" ,
582+ MODE_PRIVATE
583+ )
584+ )
572585 Log .d(" AirPodsService" , " Initializing CrossDevice" )
573- CrossDevice .init (this )
574- Log .d(" AirPodsService" , " CrossDevice initialized" )
586+ CoroutineScope (Dispatchers .IO ).launch {
587+ CrossDevice .init (this @AirPodsService)
588+ Log .d(" AirPodsService" , " CrossDevice initialized" )
589+ }
575590
576591 sharedPreferences = getSharedPreferences(" settings" , MODE_PRIVATE )
577-
592+ macAddress = sharedPreferences.getString(" mac_address" , " " ) ? : " "
593+
594+ telephonyManager = getSystemService(TELEPHONY_SERVICE ) as TelephonyManager
595+ phoneStateListener = object : PhoneStateListener () {
596+ @SuppressLint(" SwitchIntDef" )
597+ override fun onCallStateChanged (state : Int , phoneNumber : String? ) {
598+ super .onCallStateChanged(state, phoneNumber)
599+ when (state) {
600+ TelephonyManager .CALL_STATE_RINGING -> {
601+ if (CrossDevice .isAvailable && ! isConnectedLocally && earDetectionNotification.status.contains(0x00 )) takeOver()
602+ }
603+ TelephonyManager .CALL_STATE_OFFHOOK -> {
604+ if (CrossDevice .isAvailable && ! isConnectedLocally && earDetectionNotification.status.contains(0x00 )) takeOver()
605+ }
606+ }
607+ }
608+ }
609+ telephonyManager.listen(phoneStateListener, PhoneStateListener .LISTEN_CALL_STATE )
610+
611+ if (sharedPreferences.getBoolean(" show_phone_battery_in_widget" , true )) {
612+ widgetMobileBatteryEnabled = true
613+ val batteryChangedIntentFilter = IntentFilter (Intent .ACTION_BATTERY_CHANGED )
614+ batteryChangedIntentFilter.addAction(AirPodsNotifications .DISCONNECT_RECEIVERS )
615+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
616+ registerReceiver(
617+ BatteryChangedIntentReceiver ,
618+ batteryChangedIntentFilter,
619+ RECEIVER_EXPORTED
620+ )
621+ } else {
622+ registerReceiver(BatteryChangedIntentReceiver , batteryChangedIntentFilter)
623+ }
624+ }
578625 val serviceIntentFilter = IntentFilter ().apply {
579626 addAction(" android.bluetooth.device.action.ACL_CONNECTED" )
580627 addAction(" android.bluetooth.device.action.ACL_DISCONNECTED" )
@@ -605,13 +652,16 @@ class AirPodsService : Service() {
605652 putString(" name" , name)
606653 }
607654 }
608- Log .d(" AirPodsQuickSwitchServices " , CrossDevice .isAvailable.toString())
609- if (! CrossDevice .checkAirPodsConnectionStatus() ) {
655+ Log .d(" AirPodsCrossDevice " , CrossDevice .isAvailable.toString())
656+ if (! CrossDevice .isAvailable ) {
610657 Log .d(" AirPodsService" , " $name connected" )
611658 showPopup(this @AirPodsService, name.toString())
612659 connectToSocket(device!! )
613660 isConnectedLocally = true
614661 macAddress = device!! .address
662+ sharedPreferences.edit {
663+ putString(" mac_address" , macAddress)
664+ }
615665 updateNotificationContent(
616666 true ,
617667 name.toString(),
@@ -626,7 +676,30 @@ class AirPodsService : Service() {
626676 }
627677 }
628678 }
679+ val showIslandReceiver = object : BroadcastReceiver () {
680+ override fun onReceive (context : Context ? , intent : Intent ? ) {
681+ if (intent?.action == " me.kavishdevar.aln.cross_device_island" ) {
682+ showIsland(this @AirPodsService, batteryNotification.getBattery().find { it.component == BatteryComponent .LEFT }?.level!! .coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent .RIGHT }?.level!! ))
683+ } else if (intent?.action == AirPodsNotifications .Companion .DISCONNECT_RECEIVERS ) {
684+ try {
685+ context?.unregisterReceiver(this )
686+ } catch (e: Exception ) {
687+ e.printStackTrace()
688+ }
689+ }
690+ }
691+ }
629692
693+ val showIslandIntentFilter = IntentFilter ().apply {
694+ addAction(" me.kavishdevar.aln.cross_device_island" )
695+ addAction(AirPodsNotifications .DISCONNECT_RECEIVERS )
696+ }
697+
698+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
699+ registerReceiver(showIslandReceiver, showIslandIntentFilter, RECEIVER_EXPORTED )
700+ } else {
701+ registerReceiver(showIslandReceiver, showIslandIntentFilter)
702+ }
630703
631704 val deviceIntentFilter = IntentFilter ().apply {
632705 addAction(AirPodsNotifications .Companion .AIRPODS_CONNECTION_DETECTED )
@@ -641,11 +714,6 @@ class AirPodsService : Service() {
641714 registerReceiver(bluetoothReceiver, serviceIntentFilter)
642715 }
643716
644- widgetMobileBatteryEnabled = getSharedPreferences(" settings" , MODE_PRIVATE ).getBoolean(
645- " show_phone_battery_in_widget" ,
646- true
647- )
648-
649717 val bluetoothAdapter = getSystemService(BluetoothManager ::class .java).adapter
650718 if (bluetoothAdapter.isEnabled) {
651719 CoroutineScope (Dispatchers .IO ).launch {
@@ -682,8 +750,12 @@ class AirPodsService : Service() {
682750 if (profile == BluetoothProfile .A2DP ) {
683751 val connectedDevices = proxy.connectedDevices
684752 if (connectedDevices.isNotEmpty()) {
685- if (! CrossDevice .checkAirPodsConnectionStatus() ) {
753+ if (! CrossDevice .isAvailable ) {
686754 connectToSocket(device)
755+ macAddress = device.address
756+ sharedPreferences.edit {
757+ putString(" mac_address" , macAddress)
758+ }
687759 }
688760 this @AirPodsService.sendBroadcast(
689761 Intent (AirPodsNotifications .Companion .AIRPODS_CONNECTED )
@@ -720,12 +792,27 @@ class AirPodsService : Service() {
720792 }
721793 }
722794
795+ @SuppressLint(" MissingPermission" )
796+ fun takeOver () {
797+ Log .d(" AirPodsService" , " Taking over audio" )
798+ CrossDevice .sendRemotePacket(CrossDevicePackets .REQUEST_DISCONNECT .packet)
799+ Log .d(" AirPodsService" , macAddress)
800+ device = getSystemService<BluetoothManager >(BluetoothManager ::class .java).adapter.bondedDevices.find {
801+ it.address == macAddress
802+ }
803+ if (device != null ) {
804+ connectToSocket(device!! )
805+ connectAudio(this , device)
806+ }
807+ showIsland(this , batteryNotification.getBattery().find { it.component == BatteryComponent .LEFT }?.level!! .coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent .RIGHT }?.level!! ), true )
808+ }
809+
723810 @SuppressLint(" MissingPermission" , " UnspecifiedRegisterReceiverFlag" )
724811 fun connectToSocket (device : BluetoothDevice ) {
725812 HiddenApiBypass .addHiddenApiExemptions(" Landroid/bluetooth/BluetoothSocket;" )
726813 val uuid: ParcelUuid = ParcelUuid .fromString(" 74ec2172-0bad-4d01-8f77-997b2be0722a" )
727814
728- if (isConnectedLocally != true ) {
815+ if (isConnectedLocally != true && ! CrossDevice .isAvailable ) {
729816 try {
730817 socket = HiddenApiBypass .newInstance(
731818 BluetoothSocket ::class .java,
@@ -799,15 +886,6 @@ class AirPodsService : Service() {
799886 )
800887 while (socket.isConnected == true ) {
801888 socket.let {
802- val audioManager =
803- this @AirPodsService.getSystemService(AUDIO_SERVICE ) as AudioManager
804- MediaController .initialize(
805- audioManager,
806- this @AirPodsService.getSharedPreferences(
807- " settings" ,
808- MODE_PRIVATE
809- )
810- )
811889 val buffer = ByteArray (1024 )
812890 val bytesRead = it.inputStream.read(buffer)
813891 var data: ByteArray = byteArrayOf()
@@ -852,11 +930,16 @@ class AirPodsService : Service() {
852930 } else {
853931 data[0 ] == 0x00 .toByte() && data[1 ] == 0x00 .toByte()
854932 }
855-
856933 val newInEarData = listOf (
857934 data[0 ] == 0x00 .toByte(),
858935 data[1 ] == 0x00 .toByte()
859936 )
937+ if (inEarData.sorted() == listOf (false , false ) && newInEarData.sorted() != listOf (false , false ) && islandWindow?.isVisible != true ) {
938+ showIsland(this @AirPodsService, batteryNotification.getBattery().find { it.component == BatteryComponent .LEFT }?.level!! .coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent .RIGHT }?.level!! ))
939+ }
940+ if (newInEarData == listOf (false , false ) && islandWindow?.isVisible == true ) {
941+ islandWindow?.close()
942+ }
860943 if (newInEarData.contains(true ) && inEarData == listOf (
861944 false ,
862945 false
@@ -1296,6 +1379,9 @@ class AirPodsService : Service() {
12961379 e.printStackTrace()
12971380 } finally {
12981381 bluetoothAdapter.closeProfileProxy(BluetoothProfile .A2DP , proxy)
1382+ if (MediaController .pausedForCrossDevice) {
1383+ MediaController .sendPlay()
1384+ }
12991385 }
13001386 }
13011387 }
@@ -1521,6 +1607,7 @@ class AirPodsService : Service() {
15211607 } catch (e: Exception ) {
15221608 e.printStackTrace()
15231609 }
1610+ telephonyManager.listen(phoneStateListener, PhoneStateListener .LISTEN_NONE )
15241611 super .onDestroy()
15251612 }
15261613}
0 commit comments