mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-02-10 19:52:24 +00:00
widgets!
This commit is contained in:
@@ -16,6 +16,5 @@ indent_size = 4
|
|||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
max_line_length = off
|
max_line_length = off
|
||||||
|
|
||||||
[*.{py,java,r,R}]
|
[*.{py,java,r,R,kt,xml,kts}]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
|
|||||||
@@ -11,15 +11,19 @@
|
|||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.BLUETOOTH_PRIVILEGED"
|
android:name="android.permission.BLUETOOTH_PRIVILEGED"
|
||||||
tools:ignore="ProtectedPermissions" />
|
tools:ignore="ProtectedPermissions" />
|
||||||
<uses-permission android:name="android.permission.BATTERY_STATS"
|
<uses-permission
|
||||||
tools:ignore="ProtectedPermissions"/>
|
android:name="android.permission.BATTERY_STATS"
|
||||||
<uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.UPDATE_DEVICE_STATS"
|
||||||
tools:ignore="ProtectedPermissions" />
|
tools:ignore="ProtectedPermissions" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
|
<uses-permission
|
||||||
|
android:name="android.permission.BLUETOOTH_SCAN"
|
||||||
android:usesPermissionFlags="neverForLocation"
|
android:usesPermissionFlags="neverForLocation"
|
||||||
tools:ignore="UnusedAttribute" />
|
tools:ignore="UnusedAttribute" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
@@ -32,6 +36,17 @@
|
|||||||
android:theme="@style/Theme.ALN"
|
android:theme="@style/Theme.ALN"
|
||||||
tools:ignore="UnusedAttribute"
|
tools:ignore="UnusedAttribute"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
|
<receiver
|
||||||
|
android:name=".widgets.NoiseControlWidget"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/noise_control_widget_info" />
|
||||||
|
</receiver>
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".widgets.BatteryWidget"
|
android:name=".widgets.BatteryWidget"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
@@ -93,4 +108,4 @@
|
|||||||
</receiver>
|
</receiver>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
/*
|
/*
|
||||||
* AirPods like Normal (ALN) - Bringing Apple-only features to Linux and Android for seamless AirPods functionality!
|
* AirPods like Normal (ALN) - Bringing Apple-only features to Linux and Android for seamless AirPods functionality!
|
||||||
*
|
*
|
||||||
* Copyright (C) 2024 Kavish Devar
|
* Copyright (C) 2024 Kavish Devar
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as published
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
* by the Free Software Foundation, either version 3 of the License.
|
* by the Free Software Foundation, either version 3 of the License.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU Affero General Public License for more details.
|
* GNU Affero General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
@@ -28,7 +28,6 @@ import android.app.Service
|
|||||||
import android.appwidget.AppWidgetManager
|
import android.appwidget.AppWidgetManager
|
||||||
import android.bluetooth.BluetoothAdapter
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.bluetooth.BluetoothDevice
|
import android.bluetooth.BluetoothDevice
|
||||||
import android.bluetooth.BluetoothHeadset
|
|
||||||
import android.bluetooth.BluetoothManager
|
import android.bluetooth.BluetoothManager
|
||||||
import android.bluetooth.BluetoothProfile
|
import android.bluetooth.BluetoothProfile
|
||||||
import android.bluetooth.BluetoothSocket
|
import android.bluetooth.BluetoothSocket
|
||||||
@@ -42,6 +41,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.content.res.Resources
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.os.BatteryManager
|
import android.os.BatteryManager
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
@@ -51,6 +51,7 @@ import android.os.IBinder
|
|||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.ParcelUuid
|
import android.os.ParcelUuid
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.annotation.RequiresPermission
|
import androidx.annotation.RequiresPermission
|
||||||
@@ -79,18 +80,22 @@ import me.kavishdevar.aln.utils.LongPressPackets
|
|||||||
import me.kavishdevar.aln.utils.MediaController
|
import me.kavishdevar.aln.utils.MediaController
|
||||||
import me.kavishdevar.aln.utils.Window
|
import me.kavishdevar.aln.utils.Window
|
||||||
import me.kavishdevar.aln.widgets.BatteryWidget
|
import me.kavishdevar.aln.widgets.BatteryWidget
|
||||||
|
import me.kavishdevar.aln.widgets.NoiseControlWidget
|
||||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||||
|
|
||||||
object ServiceManager {
|
object ServiceManager {
|
||||||
private var service: AirPodsService? = null
|
private var service: AirPodsService? = null
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun getService(): AirPodsService? {
|
fun getService(): AirPodsService? {
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun setService(service: AirPodsService?) {
|
fun setService(service: AirPodsService?) {
|
||||||
this.service = service
|
this.service = service
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun restartService(context: Context) {
|
fun restartService(context: Context) {
|
||||||
@@ -108,12 +113,14 @@ object ServiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @Suppress("unused")
|
// @Suppress("unused")
|
||||||
class AirPodsService: Service() {
|
class AirPodsService : Service() {
|
||||||
private var macAddress = ""
|
private var macAddress = ""
|
||||||
|
|
||||||
inner class LocalBinder : Binder() {
|
inner class LocalBinder : Binder() {
|
||||||
fun getService(): AirPodsService = this@AirPodsService
|
fun getService(): AirPodsService = this@AirPodsService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var sharedPreferencesLogs: SharedPreferences
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
private val packetLogKey = "packet_log"
|
private val packetLogKey = "packet_log"
|
||||||
private val _packetLogsFlow = MutableStateFlow<Set<String>>(emptySet())
|
private val _packetLogsFlow = MutableStateFlow<Set<String>>(emptySet())
|
||||||
@@ -121,24 +128,26 @@ class AirPodsService: Service() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
sharedPreferences = getSharedPreferences("packet_logs", MODE_PRIVATE)
|
sharedPreferencesLogs = getSharedPreferences("packet_logs", MODE_PRIVATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun logPacket(packet: ByteArray, source: String) {
|
private fun logPacket(packet: ByteArray, source: String) {
|
||||||
val packetHex = packet.joinToString(" ") { "%02X".format(it) }
|
val packetHex = packet.joinToString(" ") { "%02X".format(it) }
|
||||||
val logEntry = "$source: $packetHex"
|
val logEntry = "$source: $packetHex"
|
||||||
val logs = sharedPreferences.getStringSet(packetLogKey, mutableSetOf())?.toMutableSet() ?: mutableSetOf()
|
val logs =
|
||||||
|
sharedPreferencesLogs.getStringSet(packetLogKey, mutableSetOf())?.toMutableSet()
|
||||||
|
?: mutableSetOf()
|
||||||
logs.add(logEntry)
|
logs.add(logEntry)
|
||||||
_packetLogsFlow.value = logs
|
_packetLogsFlow.value = logs
|
||||||
sharedPreferences.edit { putStringSet(packetLogKey, logs) }
|
sharedPreferencesLogs.edit { putStringSet(packetLogKey, logs) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPacketLogs(): Set<String> {
|
fun getPacketLogs(): Set<String> {
|
||||||
return sharedPreferences.getStringSet(packetLogKey, emptySet()) ?: emptySet()
|
return sharedPreferencesLogs.getStringSet(packetLogKey, emptySet()) ?: emptySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearPacketLogs() {
|
private fun clearPacketLogs() {
|
||||||
sharedPreferences.edit { remove(packetLogKey).apply() }
|
sharedPreferencesLogs.edit { remove(packetLogKey).apply() }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,18 +172,22 @@ class AirPodsService: Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("ClassName")
|
@Suppress("ClassName")
|
||||||
private object bluetoothReceiver: BroadcastReceiver() {
|
private object bluetoothReceiver : BroadcastReceiver() {
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
override fun onReceive(context: Context?, intent: Intent) {
|
override fun onReceive(context: Context?, intent: Intent) {
|
||||||
val bluetoothDevice =
|
val bluetoothDevice =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE", BluetoothDevice::class.java)
|
intent.getParcelableExtra(
|
||||||
|
"android.bluetooth.device.extra.DEVICE",
|
||||||
|
BluetoothDevice::class.java
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE") as BluetoothDevice?
|
intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE") as BluetoothDevice?
|
||||||
}
|
}
|
||||||
val action = intent.action
|
val action = intent.action
|
||||||
val context = context?.applicationContext
|
val context = context?.applicationContext
|
||||||
val name = context?.getSharedPreferences("settings", MODE_PRIVATE)?.getString("name", bluetoothDevice?.name)
|
val name = context?.getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
?.getString("name", bluetoothDevice?.name)
|
||||||
if (bluetoothDevice != null && action != null && !action.isEmpty()) {
|
if (bluetoothDevice != null && action != null && !action.isEmpty()) {
|
||||||
Log.d("AirPodsService", "Received bluetooth connection broadcast")
|
Log.d("AirPodsService", "Received bluetooth connection broadcast")
|
||||||
if (BluetoothDevice.ACTION_ACL_CONNECTED == action) {
|
if (BluetoothDevice.ACTION_ACL_CONNECTED == action) {
|
||||||
@@ -204,109 +217,27 @@ class AirPodsService: Service() {
|
|||||||
private lateinit var earReceiver: BroadcastReceiver
|
private lateinit var earReceiver: BroadcastReceiver
|
||||||
var widgetMobileBatteryEnabled = false
|
var widgetMobileBatteryEnabled = false
|
||||||
|
|
||||||
val METADATA_UNTETHERED_LEFT_CHARGING = 13
|
object BatteryChangedIntentReceiver : BroadcastReceiver() {
|
||||||
val METADATA_UNTETHERED_LEFT_BATTERY = 10
|
|
||||||
val METADATA_UNTETHERED_RIGHT_CHARGING = 14
|
|
||||||
val METADATA_UNTETHERED_RIGHT_BATTERY = 11
|
|
||||||
val METADATA_UNTETHERED_CASE_CHARGING = 15
|
|
||||||
val METADATA_UNTETHERED_CASE_BATTERY = 12
|
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
|
||||||
fun setBatteryLevels(
|
|
||||||
leftStatus: Boolean, leftLevel: Int,
|
|
||||||
rightStatus: Boolean, rightLevel: Int,
|
|
||||||
caseStatus: Boolean, caseLevel: Int,
|
|
||||||
device: BluetoothDevice
|
|
||||||
) {
|
|
||||||
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothDevice;")
|
|
||||||
|
|
||||||
HiddenApiBypass.invoke(
|
|
||||||
BluetoothDevice::class.java,
|
|
||||||
device,
|
|
||||||
"setMetadata",
|
|
||||||
METADATA_UNTETHERED_LEFT_CHARGING,
|
|
||||||
leftStatus.toString().toByteArray()
|
|
||||||
)
|
|
||||||
HiddenApiBypass.invoke(
|
|
||||||
BluetoothDevice::class.java,
|
|
||||||
device,
|
|
||||||
"setMetadata",
|
|
||||||
METADATA_UNTETHERED_LEFT_BATTERY,
|
|
||||||
leftLevel.toString().toByteArray()
|
|
||||||
)
|
|
||||||
HiddenApiBypass.invoke(
|
|
||||||
BluetoothDevice::class.java,
|
|
||||||
device,
|
|
||||||
"setMetadata",
|
|
||||||
METADATA_UNTETHERED_RIGHT_CHARGING,
|
|
||||||
rightStatus.toString().toByteArray()
|
|
||||||
)
|
|
||||||
HiddenApiBypass.invoke(
|
|
||||||
BluetoothDevice::class.java,
|
|
||||||
device,
|
|
||||||
"setMetadata",
|
|
||||||
METADATA_UNTETHERED_RIGHT_BATTERY,
|
|
||||||
rightLevel.toString().toByteArray()
|
|
||||||
)
|
|
||||||
HiddenApiBypass.invoke(
|
|
||||||
BluetoothDevice::class.java,
|
|
||||||
device,
|
|
||||||
"setMetadata",
|
|
||||||
METADATA_UNTETHERED_CASE_CHARGING,
|
|
||||||
caseStatus.toString().toByteArray()
|
|
||||||
)
|
|
||||||
HiddenApiBypass.invoke(
|
|
||||||
BluetoothDevice::class.java,
|
|
||||||
device,
|
|
||||||
"setMetadata",
|
|
||||||
METADATA_UNTETHERED_CASE_BATTERY,
|
|
||||||
caseLevel.toString().toByteArray()
|
|
||||||
)
|
|
||||||
HiddenApiBypass.invoke(
|
|
||||||
BluetoothDevice::class.java,
|
|
||||||
device,
|
|
||||||
"sendVendorSpecificHeadsetEvent",
|
|
||||||
"+IPHONEACCEV",
|
|
||||||
BluetoothHeadset.AT_CMD_TYPE_SET,
|
|
||||||
1,
|
|
||||||
leftLevel,
|
|
||||||
2,
|
|
||||||
rightLevel,
|
|
||||||
3,
|
|
||||||
caseLevel
|
|
||||||
)
|
|
||||||
|
|
||||||
// Prepare the intent to broadcast vendor-specific headset event
|
|
||||||
val intent = Intent(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT).apply {
|
|
||||||
putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD, "+IPHONEACCEV")
|
|
||||||
putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, BluetoothHeadset.AT_CMD_TYPE_SET)
|
|
||||||
putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arrayOf(
|
|
||||||
1, leftLevel,
|
|
||||||
2, rightLevel,
|
|
||||||
3, caseLevel
|
|
||||||
))
|
|
||||||
putExtra(BluetoothDevice.EXTRA_DEVICE, device)
|
|
||||||
putExtra(BluetoothDevice.EXTRA_NAME, device.name)
|
|
||||||
addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." + 76)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the broadcast to update the battery levels
|
|
||||||
sendBroadcast(intent)
|
|
||||||
|
|
||||||
// Broadcast battery level changes
|
|
||||||
val batteryIntent = Intent("android.bluet9ooth.device.action.BATTERY_LEVEL_CHANGED").apply {
|
|
||||||
putExtra(BluetoothDevice.EXTRA_DEVICE, device)
|
|
||||||
putExtra("android.bluetooth.device.extra.BATTERY_LEVEL", leftLevel) // Update with appropriate levels
|
|
||||||
}
|
|
||||||
sendBroadcast(batteryIntent)
|
|
||||||
}
|
|
||||||
|
|
||||||
object PhoneBatteryReceiver: BroadcastReceiver() {
|
|
||||||
override fun onReceive(context: Context?, intent: Intent) {
|
override fun onReceive(context: Context?, intent: Intent) {
|
||||||
if (intent.action == Intent.ACTION_BATTERY_CHANGED) {
|
if (intent.action == Intent.ACTION_BATTERY_CHANGED) {
|
||||||
ServiceManager.getService()?.updateBatteryWidget()
|
val level = intent.getIntExtra("level", 0)
|
||||||
}
|
val scale = intent.getIntExtra("scale", 100)
|
||||||
else if (intent.action == AirPodsNotifications.DISCONNECT_RECEIVERS) {
|
val batteryPct = level * 100 / scale
|
||||||
|
val charging = intent.getIntExtra(
|
||||||
|
BatteryManager.EXTRA_STATUS,
|
||||||
|
-1
|
||||||
|
) == BatteryManager.BATTERY_STATUS_CHARGING
|
||||||
|
if (ServiceManager.getService()?.widgetMobileBatteryEnabled == true) {
|
||||||
|
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||||
|
val componentName = ComponentName(context!!, BatteryWidget::class.java)
|
||||||
|
val widgetIds = appWidgetManager.getAppWidgetIds(componentName)
|
||||||
|
val remoteViews = RemoteViews(context.packageName, R.layout.battery_widget)
|
||||||
|
remoteViews.setTextViewText(R.id.phone_battery_widget, "$batteryPct%")
|
||||||
|
remoteViews.setProgressBar(R.id.phone_battery_progress, 100, batteryPct, false)
|
||||||
|
|
||||||
|
appWidgetManager.updateAppWidget(widgetIds, remoteViews)
|
||||||
|
}
|
||||||
|
} else if (intent.action == AirPodsNotifications.DISCONNECT_RECEIVERS) {
|
||||||
try {
|
try {
|
||||||
context?.unregisterReceiver(this)
|
context?.unregisterReceiver(this)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -315,35 +246,12 @@ class AirPodsService: Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val phoneBatteryIntentFilter = IntentFilter().apply {
|
|
||||||
addAction(Intent.ACTION_BATTERY_CHANGED)
|
|
||||||
addAction(AirPodsNotifications.DISCONNECT_RECEIVERS)
|
|
||||||
}
|
|
||||||
fun setPhoneBatteryInWidget(enabled: Boolean) {
|
fun setPhoneBatteryInWidget(enabled: Boolean) {
|
||||||
widgetMobileBatteryEnabled = enabled
|
widgetMobileBatteryEnabled = enabled
|
||||||
if (enabled) {
|
|
||||||
try {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
registerReceiver(
|
|
||||||
PhoneBatteryReceiver,
|
|
||||||
phoneBatteryIntentFilter,
|
|
||||||
RECEIVER_EXPORTED
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
registerReceiver(PhoneBatteryReceiver, phoneBatteryIntentFilter)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
unregisterReceiver(PhoneBatteryReceiver)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateBatteryWidget()
|
updateBatteryWidget()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
fun scanForAirPods(bluetoothAdapter: BluetoothAdapter): Flow<List<ScanResult>> = callbackFlow {
|
fun scanForAirPods(bluetoothAdapter: BluetoothAdapter): Flow<List<ScanResult>> = callbackFlow {
|
||||||
val bluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner
|
val bluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner
|
||||||
@@ -392,7 +300,10 @@ class AirPodsService: Service() {
|
|||||||
|
|
||||||
val notificationIntent = Intent(this, MainActivity::class.java)
|
val notificationIntent = Intent(this, MainActivity::class.java)
|
||||||
val pendingIntent = PendingIntent.getActivity(
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
this,
|
||||||
|
0,
|
||||||
|
notificationIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
|
|
||||||
val notification = NotificationCompat.Builder(this, "background_service_status")
|
val notification = NotificationCompat.Builder(this, "background_service_status")
|
||||||
@@ -434,15 +345,22 @@ class AirPodsService: Service() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
fun updateBatteryWidget() {
|
fun updateBatteryWidget() {
|
||||||
val appWidgetManager = AppWidgetManager.getInstance(this)
|
val appWidgetManager = AppWidgetManager.getInstance(this)
|
||||||
val componentName = ComponentName(this, BatteryWidget::class.java)
|
val componentName = ComponentName(this, BatteryWidget::class.java)
|
||||||
val widgetIds = appWidgetManager.getAppWidgetIds(componentName)
|
val widgetIds = appWidgetManager.getAppWidgetIds(componentName)
|
||||||
|
|
||||||
val remoteViews = RemoteViews(packageName, R.layout.battery_widget).also {
|
val remoteViews = RemoteViews(packageName, R.layout.battery_widget).also {
|
||||||
val leftBattery = batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT }
|
val openActivityIntent = PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
val rightBattery = batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT }
|
it.setOnClickPendingIntent(R.id.battery_widget, openActivityIntent)
|
||||||
val caseBattery = batteryNotification.getBattery().find { it.component == BatteryComponent.CASE }
|
|
||||||
|
val leftBattery =
|
||||||
|
batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT }
|
||||||
|
val rightBattery =
|
||||||
|
batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT }
|
||||||
|
val caseBattery =
|
||||||
|
batteryNotification.getBattery().find { it.component == BatteryComponent.CASE }
|
||||||
|
|
||||||
it.setTextViewText(
|
it.setTextViewText(
|
||||||
R.id.left_battery_widget,
|
R.id.left_battery_widget,
|
||||||
@@ -501,8 +419,10 @@ class AirPodsService: Service() {
|
|||||||
)
|
)
|
||||||
if (widgetMobileBatteryEnabled) {
|
if (widgetMobileBatteryEnabled) {
|
||||||
val batteryManager = getSystemService<BatteryManager>(BatteryManager::class.java)
|
val batteryManager = getSystemService<BatteryManager>(BatteryManager::class.java)
|
||||||
val batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
|
val batteryLevel =
|
||||||
val charging = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS) == BatteryManager.BATTERY_STATUS_CHARGING
|
batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
|
||||||
|
val charging =
|
||||||
|
batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS) == BatteryManager.BATTERY_STATUS_CHARGING
|
||||||
it.setTextViewText(
|
it.setTextViewText(
|
||||||
R.id.phone_battery_widget,
|
R.id.phone_battery_widget,
|
||||||
"$batteryLevel%"
|
"$batteryLevel%"
|
||||||
@@ -522,40 +442,105 @@ class AirPodsService: Service() {
|
|||||||
appWidgetManager.updateAppWidget(widgetIds, remoteViews)
|
appWidgetManager.updateAppWidget(widgetIds, remoteViews)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateNoiseControlWidget() {
|
||||||
|
val appWidgetManager = AppWidgetManager.getInstance(this)
|
||||||
|
val componentName = ComponentName(this, NoiseControlWidget::class.java)
|
||||||
|
val widgetIds = appWidgetManager.getAppWidgetIds(componentName)
|
||||||
|
val remoteViews = RemoteViews(packageName, R.layout.noise_control_widget).also {
|
||||||
|
val ancStatus = ancNotification.status
|
||||||
|
it.setInt(
|
||||||
|
R.id.widget_off_button,
|
||||||
|
"setBackgroundResource",
|
||||||
|
if (ancStatus == 1) R.drawable.widget_button_checked_shape_start else R.drawable.widget_button_shape_start
|
||||||
|
)
|
||||||
|
it.setInt(
|
||||||
|
R.id.widget_transparency_button,
|
||||||
|
"setBackgroundResource",
|
||||||
|
if (ancStatus == 3) (if (sharedPreferences.getBoolean("off_listening_mode", true)) R.drawable.widget_button_checked_shape_middle else R.drawable.widget_button_checked_shape_start) else (if (sharedPreferences.getBoolean("off_listening_mode", true)) R.drawable.widget_button_shape_middle else R.drawable.widget_button_shape_start)
|
||||||
|
)
|
||||||
|
it.setInt(
|
||||||
|
R.id.widget_adaptive_button,
|
||||||
|
"setBackgroundResource",
|
||||||
|
if (ancStatus == 4) R.drawable.widget_button_checked_shape_middle else R.drawable.widget_button_shape_middle
|
||||||
|
)
|
||||||
|
it.setInt(
|
||||||
|
R.id.widget_anc_button,
|
||||||
|
"setBackgroundResource",
|
||||||
|
if (ancStatus == 2) R.drawable.widget_button_checked_shape_end else R.drawable.widget_button_shape_end
|
||||||
|
)
|
||||||
|
it.setViewVisibility(
|
||||||
|
R.id.widget_off_button,
|
||||||
|
if (sharedPreferences.getBoolean("off_listening_mode", true)) View.VISIBLE else View.GONE
|
||||||
|
)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
it.setViewLayoutMargin(
|
||||||
|
R.id.widget_transparency_button,
|
||||||
|
RemoteViews.MARGIN_START,
|
||||||
|
if (sharedPreferences.getBoolean("off_listening_mode", true)) 2f else 12f,
|
||||||
|
TypedValue.COMPLEX_UNIT_DIP
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
it.setViewPadding(
|
||||||
|
R.id.widget_transparency_button,
|
||||||
|
if (sharedPreferences.getBoolean("off_listening_mode", true)) 2.dpToPx() else 12.dpToPx(),
|
||||||
|
12.dpToPx(),
|
||||||
|
2.dpToPx(),
|
||||||
|
12.dpToPx()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appWidgetManager.updateAppWidget(widgetIds, remoteViews)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
fun updateNotificationContent(connected: Boolean, airpodsName: String? = null, batteryList: List<Battery>? = null) {
|
fun updateNotificationContent(
|
||||||
|
connected: Boolean,
|
||||||
|
airpodsName: String? = null,
|
||||||
|
batteryList: List<Battery>? = null
|
||||||
|
) {
|
||||||
val notificationManager = getSystemService(NotificationManager::class.java)
|
val notificationManager = getSystemService(NotificationManager::class.java)
|
||||||
var updatedNotification: Notification? = null
|
var updatedNotification: Notification? = null
|
||||||
|
|
||||||
val notificationIntent = Intent(this, MainActivity::class.java)
|
val notificationIntent = Intent(this, MainActivity::class.java)
|
||||||
val pendingIntent = PendingIntent.getActivity(
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
this,
|
||||||
|
0,
|
||||||
|
notificationIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
|
|
||||||
if (connected) {
|
if (connected) {
|
||||||
updatedNotification = NotificationCompat.Builder(this, "background_service_status")
|
updatedNotification = NotificationCompat.Builder(this, "background_service_status")
|
||||||
.setSmallIcon(R.drawable.airpods)
|
.setSmallIcon(R.drawable.airpods)
|
||||||
.setContentTitle(airpodsName)
|
.setContentTitle(airpodsName)
|
||||||
.setContentText("""${batteryList?.find { it.component == BatteryComponent.LEFT }?.let {
|
.setContentText(
|
||||||
if (it.status != BatteryStatus.DISCONNECTED) {
|
"""${
|
||||||
"L: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
batteryList?.find { it.component == BatteryComponent.LEFT }?.let {
|
||||||
} else {
|
if (it.status != BatteryStatus.DISCONNECTED) {
|
||||||
""
|
"L: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||||
}
|
} else {
|
||||||
} ?: ""} ${batteryList?.find { it.component == BatteryComponent.RIGHT }?.let {
|
""
|
||||||
if (it.status != BatteryStatus.DISCONNECTED) {
|
}
|
||||||
"R: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
} ?: ""
|
||||||
} else {
|
} ${
|
||||||
""
|
batteryList?.find { it.component == BatteryComponent.RIGHT }?.let {
|
||||||
}
|
if (it.status != BatteryStatus.DISCONNECTED) {
|
||||||
} ?: ""} ${batteryList?.find { it.component == BatteryComponent.CASE }?.let {
|
"R: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||||
if (it.status != BatteryStatus.DISCONNECTED) {
|
} else {
|
||||||
"Case: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
""
|
||||||
} else {
|
}
|
||||||
""
|
} ?: ""
|
||||||
}
|
} ${
|
||||||
} ?: ""}""")
|
batteryList?.find { it.component == BatteryComponent.CASE }?.let {
|
||||||
.setContentIntent(pendingIntent)
|
if (it.status != BatteryStatus.DISCONNECTED) {
|
||||||
|
"Case: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
} ?: ""
|
||||||
|
}""")
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
.setCategory(Notification.CATEGORY_SERVICE)
|
.setCategory(Notification.CATEGORY_SERVICE)
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
@@ -583,11 +568,13 @@ class AirPodsService: Service() {
|
|||||||
Log.d("AirPodsService", "Service started")
|
Log.d("AirPodsService", "Service started")
|
||||||
ServiceManager.setService(this)
|
ServiceManager.setService(this)
|
||||||
startForegroundNotification()
|
startForegroundNotification()
|
||||||
|
|
||||||
Log.d("AirPodsService", "Initializing CrossDevice")
|
Log.d("AirPodsService", "Initializing CrossDevice")
|
||||||
CrossDevice.init(this)
|
CrossDevice.init(this)
|
||||||
Log.d("AirPodsService", "CrossDevice initialized")
|
Log.d("AirPodsService", "CrossDevice initialized")
|
||||||
|
|
||||||
|
sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
|
||||||
val serviceIntentFilter = IntentFilter().apply {
|
val serviceIntentFilter = IntentFilter().apply {
|
||||||
addAction("android.bluetooth.device.action.ACL_CONNECTED")
|
addAction("android.bluetooth.device.action.ACL_CONNECTED")
|
||||||
addAction("android.bluetooth.device.action.ACL_DISCONNECTED")
|
addAction("android.bluetooth.device.action.ACL_DISCONNECTED")
|
||||||
@@ -601,7 +588,7 @@ class AirPodsService: Service() {
|
|||||||
addAction("android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED")
|
addAction("android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED")
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionReceiver = object: BroadcastReceiver() {
|
connectionReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
if (intent?.action == AirPodsNotifications.Companion.AIRPODS_CONNECTION_DETECTED) {
|
if (intent?.action == AirPodsNotifications.Companion.AIRPODS_CONNECTION_DETECTED) {
|
||||||
device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
@@ -611,9 +598,12 @@ class AirPodsService: Service() {
|
|||||||
}
|
}
|
||||||
val name = this@AirPodsService.getSharedPreferences("settings", MODE_PRIVATE)
|
val name = this@AirPodsService.getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
.getString("name", device?.name)
|
.getString("name", device?.name)
|
||||||
if (this@AirPodsService.getSharedPreferences("settings", MODE_PRIVATE).getString("name", null) == null) {
|
if (this@AirPodsService.getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
.getString("name", null) == null
|
||||||
|
) {
|
||||||
this@AirPodsService.getSharedPreferences("settings", MODE_PRIVATE).edit {
|
this@AirPodsService.getSharedPreferences("settings", MODE_PRIVATE).edit {
|
||||||
putString("name", name)}
|
putString("name", name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Log.d("AirPodsQuickSwitchServices", CrossDevice.isAvailable.toString())
|
Log.d("AirPodsQuickSwitchServices", CrossDevice.isAvailable.toString())
|
||||||
if (!CrossDevice.checkAirPodsConnectionStatus()) {
|
if (!CrossDevice.checkAirPodsConnectionStatus()) {
|
||||||
@@ -622,7 +612,11 @@ class AirPodsService: Service() {
|
|||||||
connectToSocket(device!!)
|
connectToSocket(device!!)
|
||||||
isConnectedLocally = true
|
isConnectedLocally = true
|
||||||
macAddress = device!!.address
|
macAddress = device!!.address
|
||||||
updateNotificationContent(true, name.toString(), batteryNotification.getBattery())
|
updateNotificationContent(
|
||||||
|
true,
|
||||||
|
name.toString(),
|
||||||
|
batteryNotification.getBattery()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else if (intent?.action == AirPodsNotifications.Companion.AIRPODS_DISCONNECTED) {
|
} else if (intent?.action == AirPodsNotifications.Companion.AIRPODS_DISCONNECTED) {
|
||||||
device = null
|
device = null
|
||||||
@@ -647,14 +641,11 @@ class AirPodsService: Service() {
|
|||||||
registerReceiver(bluetoothReceiver, serviceIntentFilter)
|
registerReceiver(bluetoothReceiver, serviceIntentFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
widgetMobileBatteryEnabled = getSharedPreferences("settings", MODE_PRIVATE).getBoolean("show_phone_battery_in_widget", true)
|
widgetMobileBatteryEnabled = getSharedPreferences("settings", MODE_PRIVATE).getBoolean(
|
||||||
if (widgetMobileBatteryEnabled) {
|
"show_phone_battery_in_widget",
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
true
|
||||||
registerReceiver(PhoneBatteryReceiver, phoneBatteryIntentFilter, RECEIVER_EXPORTED)
|
)
|
||||||
} else {
|
|
||||||
registerReceiver(PhoneBatteryReceiver, phoneBatteryIntentFilter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter
|
val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter
|
||||||
if (bluetoothAdapter.isEnabled) {
|
if (bluetoothAdapter.isEnabled) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
@@ -663,10 +654,12 @@ class AirPodsService: Service() {
|
|||||||
scanResults.forEach { scanResult ->
|
scanResults.forEach { scanResult ->
|
||||||
val device = scanResult.device
|
val device = scanResult.device
|
||||||
device.fetchUuidsWithSdp()
|
device.fetchUuidsWithSdp()
|
||||||
val manufacturerData = scanResult.scanRecord?.manufacturerSpecificData?.get(0x004C)
|
val manufacturerData =
|
||||||
|
scanResult.scanRecord?.manufacturerSpecificData?.get(0x004C)
|
||||||
if (manufacturerData != null && manufacturerData != lastData) {
|
if (manufacturerData != null && manufacturerData != lastData) {
|
||||||
lastData = manufacturerData
|
lastData = manufacturerData
|
||||||
val formattedHex = manufacturerData.joinToString(" ") { "%02X".format(it) }
|
val formattedHex =
|
||||||
|
manufacturerData.joinToString(" ") { "%02X".format(it) }
|
||||||
val rssi = scanResult.rssi
|
val rssi = scanResult.rssi
|
||||||
Log.d(
|
Log.d(
|
||||||
"AirPodsBLEService",
|
"AirPodsBLEService",
|
||||||
@@ -680,8 +673,7 @@ class AirPodsService: Service() {
|
|||||||
|
|
||||||
bluetoothAdapter.bondedDevices.forEach { device ->
|
bluetoothAdapter.bondedDevices.forEach { device ->
|
||||||
device.fetchUuidsWithSdp()
|
device.fetchUuidsWithSdp()
|
||||||
if (device.uuids != null)
|
if (device.uuids != null) {
|
||||||
{
|
|
||||||
if (device.uuids.contains(ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a"))) {
|
if (device.uuids.contains(ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a"))) {
|
||||||
bluetoothAdapter.getProfileProxy(
|
bluetoothAdapter.getProfileProxy(
|
||||||
this,
|
this,
|
||||||
@@ -720,7 +712,10 @@ class AirPodsService: Service() {
|
|||||||
|
|
||||||
fun manuallyCheckForAudioSource() {
|
fun manuallyCheckForAudioSource() {
|
||||||
if (earDetectionNotification.status[0] != 0.toByte() && earDetectionNotification.status[1] != 0.toByte()) {
|
if (earDetectionNotification.status[0] != 0.toByte() && earDetectionNotification.status[1] != 0.toByte()) {
|
||||||
Log.d("AirPodsService", "For some reason, Android connected to the audio profile itself even after disconnecting. Disconnecting audio profile again!")
|
Log.d(
|
||||||
|
"AirPodsService",
|
||||||
|
"For some reason, Android connected to the audio profile itself even after disconnecting. Disconnecting audio profile again!"
|
||||||
|
)
|
||||||
disconnectAudio(this, device)
|
disconnectAudio(this, device)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -806,7 +801,13 @@ class AirPodsService: Service() {
|
|||||||
socket.let {
|
socket.let {
|
||||||
val audioManager =
|
val audioManager =
|
||||||
this@AirPodsService.getSystemService(AUDIO_SERVICE) as AudioManager
|
this@AirPodsService.getSystemService(AUDIO_SERVICE) as AudioManager
|
||||||
MediaController.initialize(audioManager, this@AirPodsService.getSharedPreferences("settings", MODE_PRIVATE))
|
MediaController.initialize(
|
||||||
|
audioManager,
|
||||||
|
this@AirPodsService.getSharedPreferences(
|
||||||
|
"settings",
|
||||||
|
MODE_PRIVATE
|
||||||
|
)
|
||||||
|
)
|
||||||
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()
|
||||||
@@ -904,7 +905,10 @@ class AirPodsService: Service() {
|
|||||||
true
|
true
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Log.d("AirPods Parser", "User put in both AirPods from just one.")
|
Log.d(
|
||||||
|
"AirPods Parser",
|
||||||
|
"User put in both AirPods from just one."
|
||||||
|
)
|
||||||
MediaController.userPlayedTheMedia = false
|
MediaController.userPlayedTheMedia = false
|
||||||
}
|
}
|
||||||
if (newInEarData.contains(false) && inEarData == listOf(
|
if (newInEarData.contains(false) && inEarData == listOf(
|
||||||
@@ -912,7 +916,10 @@ class AirPodsService: Service() {
|
|||||||
true
|
true
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Log.d("AirPods Parser", "User took one of two out.")
|
Log.d(
|
||||||
|
"AirPods Parser",
|
||||||
|
"User took one of two out."
|
||||||
|
)
|
||||||
MediaController.userPlayedTheMedia = false
|
MediaController.userPlayedTheMedia = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -924,7 +931,10 @@ class AirPodsService: Service() {
|
|||||||
Log.d("AirPods Parser", "hi")
|
Log.d("AirPods Parser", "hi")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Log.d("AirPods Parser", "this shouldn't be run if the last log was 'hi'.")
|
Log.d(
|
||||||
|
"AirPods Parser",
|
||||||
|
"this shouldn't be run if the last log was 'hi'."
|
||||||
|
)
|
||||||
|
|
||||||
inEarData = newInEarData
|
inEarData = newInEarData
|
||||||
|
|
||||||
@@ -935,7 +945,7 @@ class AirPodsService: Service() {
|
|||||||
MediaController.iPausedTheMedia = false
|
MediaController.iPausedTheMedia = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
MediaController.sendPause()
|
MediaController.sendPause()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -958,9 +968,8 @@ class AirPodsService: Service() {
|
|||||||
CrossDevice.sendRemotePacket(data)
|
CrossDevice.sendRemotePacket(data)
|
||||||
CrossDevice.ancBytes = data
|
CrossDevice.ancBytes = data
|
||||||
ancNotification.setStatus(data)
|
ancNotification.setStatus(data)
|
||||||
sendBroadcast(Intent(AirPodsNotifications.Companion.ANC_DATA).apply {
|
sendANCBroadcast()
|
||||||
putExtra("data", ancNotification.status)
|
updateNoiseControlWidget()
|
||||||
})
|
|
||||||
Log.d("AirPods Parser", "ANC: ${ancNotification.status}")
|
Log.d("AirPods Parser", "ANC: ${ancNotification.status}")
|
||||||
} else if (batteryNotification.isBatteryData(data)) {
|
} else if (batteryNotification.isBatteryData(data)) {
|
||||||
CrossDevice.sendRemotePacket(data)
|
CrossDevice.sendRemotePacket(data)
|
||||||
@@ -992,15 +1001,6 @@ class AirPodsService: Service() {
|
|||||||
} else {
|
} else {
|
||||||
connectAudio(this@AirPodsService, device)
|
connectAudio(this@AirPodsService, device)
|
||||||
}
|
}
|
||||||
// setBatteryLevels(
|
|
||||||
// batteryNotification.getBattery()[0].status == 1,
|
|
||||||
// batteryNotification.getBattery()[0].level,
|
|
||||||
// batteryNotification.getBattery()[1].status == 1,
|
|
||||||
// batteryNotification.getBattery()[1].level,
|
|
||||||
// batteryNotification.getBattery()[2].status == 1,
|
|
||||||
// batteryNotification.getBattery()[2].level,
|
|
||||||
// device
|
|
||||||
// )
|
|
||||||
} else if (conversationAwarenessNotification.isConversationalAwarenessData(
|
} else if (conversationAwarenessNotification.isConversationalAwarenessData(
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
@@ -1090,12 +1090,15 @@ class AirPodsService: Service() {
|
|||||||
1 -> {
|
1 -> {
|
||||||
sendPacket(Enums.NOISE_CANCELLATION_OFF.value)
|
sendPacket(Enums.NOISE_CANCELLATION_OFF.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
2 -> {
|
2 -> {
|
||||||
sendPacket(Enums.NOISE_CANCELLATION_ON.value)
|
sendPacket(Enums.NOISE_CANCELLATION_ON.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
3 -> {
|
3 -> {
|
||||||
sendPacket(Enums.NOISE_CANCELLATION_TRANSPARENCY.value)
|
sendPacket(Enums.NOISE_CANCELLATION_TRANSPARENCY.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
4 -> {
|
4 -> {
|
||||||
sendPacket(Enums.NOISE_CANCELLATION_ADAPTIVE.value)
|
sendPacket(Enums.NOISE_CANCELLATION_ADAPTIVE.value)
|
||||||
}
|
}
|
||||||
@@ -1107,52 +1110,118 @@ class AirPodsService: Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setOffListeningMode(enabled: Boolean) {
|
fun setOffListeningMode(enabled: Boolean) {
|
||||||
sendPacket(byteArrayOf(0x04, 0x00 ,0x04, 0x00, 0x09, 0x00, 0x34, if (enabled) 0x01 else 0x02, 0x00, 0x00, 0x00))
|
sendPacket(
|
||||||
|
byteArrayOf(
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x34,
|
||||||
|
if (enabled) 0x01 else 0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00
|
||||||
|
)
|
||||||
|
)
|
||||||
|
updateNoiseControlWidget()
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
)
|
||||||
sendPacket(bytes)
|
sendPacket(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPressSpeed(speed: Int) {
|
fun setPressSpeed(speed: Int) {
|
||||||
// 0x00 = default, 0x01 = slower, 0x02 = slowest
|
// 0x00 = default, 0x01 = slower, 0x02 = slowest
|
||||||
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)
|
||||||
sendPacket(bytes)
|
sendPacket(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPressAndHoldDuration(speed: Int) {
|
fun setPressAndHoldDuration(speed: Int) {
|
||||||
// 0 - default, 1 - slower, 2 - slowest
|
// 0 - default, 1 - slower, 2 - slowest
|
||||||
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)
|
||||||
sendPacket(bytes)
|
sendPacket(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setVolumeSwipeSpeed(speed: Int) {
|
fun setVolumeSwipeSpeed(speed: Int) {
|
||||||
// 0 - default, 1 - longer, 2 - longest
|
// 0 - default, 1 - longer, 2 - longest
|
||||||
val bytes = byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x23, speed.toByte(), 0x00, 0x00, 0x00)
|
val bytes =
|
||||||
Log.d("AirPodsService", "Setting volume swipe speed to $speed by packet ${bytes.joinToString(" ") { "%02X".format(it) }}")
|
byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x23, speed.toByte(), 0x00, 0x00, 0x00)
|
||||||
|
Log.d(
|
||||||
|
"AirPodsService",
|
||||||
|
"Setting volume swipe speed to $speed by packet ${
|
||||||
|
bytes.joinToString(" ") {
|
||||||
|
"%02X".format(
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
)
|
||||||
sendPacket(bytes)
|
sendPacket(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
)
|
||||||
sendPacket(bytes)
|
sendPacket(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
)
|
||||||
sendPacket(bytes)
|
sendPacket(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
sendPacket(bytes)
|
sendPacket(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -1180,7 +1249,8 @@ class AirPodsService: Service() {
|
|||||||
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()
|
||||||
@@ -1190,14 +1260,15 @@ class AirPodsService: Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
@@ -1207,7 +1278,7 @@ class AirPodsService: Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(profile: Int) { }
|
override fun onServiceDisconnected(profile: Int) {}
|
||||||
}, BluetoothProfile.HEADSET)
|
}, BluetoothProfile.HEADSET)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1218,7 +1289,8 @@ class AirPodsService: Service() {
|
|||||||
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()
|
||||||
@@ -1228,14 +1300,15 @@ class AirPodsService: Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
@@ -1245,14 +1318,16 @@ class AirPodsService: Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
nameBytes.size.toByte(), 0x00) + nameBytes
|
0x04, 0x00, 0x04, 0x00, 0x1a, 0x00, 0x01,
|
||||||
|
nameBytes.size.toByte(), 0x00
|
||||||
|
) + nameBytes
|
||||||
sendPacket(bytes)
|
sendPacket(bytes)
|
||||||
val hex = bytes.joinToString(" ") { "%02X".format(it) }
|
val hex = bytes.joinToString(" ") { "%02X".format(it) }
|
||||||
updateNotificationContent(true, name, batteryNotification.getBattery())
|
updateNotificationContent(true, name, batteryNotification.getBattery())
|
||||||
@@ -1263,7 +1338,8 @@ class AirPodsService: Service() {
|
|||||||
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()
|
||||||
sendPacket(bytes)
|
sendPacket(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()
|
||||||
sendPacket(bytes)
|
sendPacket(bytes)
|
||||||
}
|
}
|
||||||
@@ -1273,6 +1349,7 @@ class AirPodsService: Service() {
|
|||||||
val bytes = hex.split(" ").map { it.toInt(16).toByte() }.toByteArray()
|
val bytes = hex.split(" ").map { it.toInt(16).toByte() }.toByteArray()
|
||||||
sendPacket(bytes)
|
sendPacket(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findChangedIndex(oldArray: BooleanArray, newArray: BooleanArray): Int {
|
fun findChangedIndex(oldArray: BooleanArray, newArray: BooleanArray): Int {
|
||||||
for (i in oldArray.indices) {
|
for (i in oldArray.indices) {
|
||||||
if (oldArray[i] != newArray[i]) {
|
if (oldArray[i] != newArray[i]) {
|
||||||
@@ -1281,7 +1358,12 @@ class AirPodsService: Service() {
|
|||||||
}
|
}
|
||||||
throw IllegalArgumentException("No element has changed")
|
throw IllegalArgumentException("No element has changed")
|
||||||
}
|
}
|
||||||
fun updateLongPress(oldLongPressArray: BooleanArray, newLongPressArray: BooleanArray, offListeningMode: Boolean) {
|
|
||||||
|
fun updateLongPress(
|
||||||
|
oldLongPressArray: BooleanArray,
|
||||||
|
newLongPressArray: BooleanArray,
|
||||||
|
offListeningMode: Boolean
|
||||||
|
) {
|
||||||
if (oldLongPressArray.contentEquals(newLongPressArray)) {
|
if (oldLongPressArray.contentEquals(newLongPressArray)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1391,6 +1473,7 @@ class AirPodsService: Service() {
|
|||||||
LongPressPackets.DISABLE_ANC_OFF_DISABLED.value
|
LongPressPackets.DISABLE_ANC_OFF_DISABLED.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
2 -> {
|
2 -> {
|
||||||
packet = if (newLongPressArray[2]) {
|
packet = if (newLongPressArray[2]) {
|
||||||
LongPressPackets.ENABLE_EVERYTHING_OFF_DISABLED.value
|
LongPressPackets.ENABLE_EVERYTHING_OFF_DISABLED.value
|
||||||
@@ -1398,6 +1481,7 @@ class AirPodsService: Service() {
|
|||||||
LongPressPackets.DISABLE_TRANSPARENCY_OFF_DISABLED.value
|
LongPressPackets.DISABLE_TRANSPARENCY_OFF_DISABLED.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
3 -> {
|
3 -> {
|
||||||
packet = if (newLongPressArray[3]) {
|
packet = if (newLongPressArray[3]) {
|
||||||
LongPressPackets.ENABLE_EVERYTHING_OFF_DISABLED.value
|
LongPressPackets.ENABLE_EVERYTHING_OFF_DISABLED.value
|
||||||
@@ -1439,4 +1523,9 @@ class AirPodsService: Service() {
|
|||||||
}
|
}
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Int.dpToPx(): Int {
|
||||||
|
val density = Resources.getSystem().displayMetrics.density
|
||||||
|
return (this * density).toInt()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
/*
|
/*
|
||||||
* AirPods like Normal (ALN) - Bringing Apple-only features to Linux and Android for seamless AirPods functionality!
|
* AirPods like Normal (ALN) - Bringing Apple-only features to Linux and Android for seamless AirPods functionality!
|
||||||
*
|
*
|
||||||
* Copyright (C) 2024 Kavish Devar
|
* Copyright (C) 2024 Kavish Devar
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as published
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
* by the Free Software Foundation, either version 3 of the License.
|
* by the Free Software Foundation, either version 3 of the License.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU Affero General Public License for more details.
|
* GNU Affero General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
@@ -24,11 +24,8 @@ import android.appwidget.AppWidgetManager
|
|||||||
import android.appwidget.AppWidgetProvider
|
import android.appwidget.AppWidgetProvider
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.util.Log
|
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.core.graphics.createBitmap
|
|
||||||
import me.kavishdevar.aln.MainActivity
|
import me.kavishdevar.aln.MainActivity
|
||||||
import me.kavishdevar.aln.R
|
import me.kavishdevar.aln.R
|
||||||
import me.kavishdevar.aln.services.ServiceManager
|
import me.kavishdevar.aln.services.ServiceManager
|
||||||
@@ -39,29 +36,6 @@ class BatteryWidget : AppWidgetProvider() {
|
|||||||
appWidgetManager: AppWidgetManager,
|
appWidgetManager: AppWidgetManager,
|
||||||
appWidgetIds: IntArray
|
appWidgetIds: IntArray
|
||||||
) {
|
) {
|
||||||
for (appWidgetId in appWidgetIds) {
|
ServiceManager.getService()?.updateBatteryWidget()
|
||||||
updateAppWidget(context, appWidgetManager, appWidgetId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onEnabled(context: Context) {
|
|
||||||
updateAppWidget(context, AppWidgetManager.getInstance(context), 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
internal fun updateAppWidget(
|
|
||||||
context: Context,
|
|
||||||
appWidgetManager: AppWidgetManager,
|
|
||||||
appWidgetId: Int
|
|
||||||
) {
|
|
||||||
val service = ServiceManager.getService()
|
|
||||||
val views = RemoteViews(context.packageName, R.layout.battery_widget)
|
|
||||||
|
|
||||||
service?.updateBatteryWidget()
|
|
||||||
|
|
||||||
val openActivityIntent = PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
|
||||||
views.setOnClickPendingIntent(R.id.battery_widget, openActivityIntent)
|
|
||||||
|
|
||||||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* AirPods like Normal (ALN) - Bringing Apple-only features to Linux and Android for seamless AirPods functionality!
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Kavish Devar
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package me.kavishdevar.aln.widgets
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.appwidget.AppWidgetProvider
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.widget.RemoteViews
|
||||||
|
import me.kavishdevar.aln.R
|
||||||
|
import me.kavishdevar.aln.services.ServiceManager
|
||||||
|
|
||||||
|
class NoiseControlWidget : AppWidgetProvider() {
|
||||||
|
override fun onUpdate(
|
||||||
|
context: Context,
|
||||||
|
appWidgetManager: AppWidgetManager,
|
||||||
|
appWidgetIds: IntArray
|
||||||
|
) {
|
||||||
|
val views = RemoteViews(context.packageName, R.layout.noise_control_widget)
|
||||||
|
|
||||||
|
val offIntent = Intent(context, NoiseControlWidget::class.java).apply {
|
||||||
|
action = "ACTION_SET_ANC_MODE"
|
||||||
|
putExtra("ANC_MODE", 1)
|
||||||
|
}
|
||||||
|
val transparencyIntent = Intent(context, NoiseControlWidget::class.java).apply {
|
||||||
|
action = "ACTION_SET_ANC_MODE"
|
||||||
|
putExtra("ANC_MODE", 3)
|
||||||
|
}
|
||||||
|
val adaptiveIntent = Intent(context, NoiseControlWidget::class.java).apply {
|
||||||
|
action = "ACTION_SET_ANC_MODE"
|
||||||
|
putExtra("ANC_MODE", 4)
|
||||||
|
}
|
||||||
|
val ancIntent = Intent(context, NoiseControlWidget::class.java).apply {
|
||||||
|
action = "ACTION_SET_ANC_MODE"
|
||||||
|
putExtra("ANC_MODE", 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
views.setOnClickPendingIntent(
|
||||||
|
R.id.widget_off_button,
|
||||||
|
PendingIntent.getBroadcast(context, 0, offIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
)
|
||||||
|
views.setOnClickPendingIntent(
|
||||||
|
R.id.widget_transparency_button,
|
||||||
|
PendingIntent.getBroadcast(context, 1, transparencyIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
)
|
||||||
|
views.setOnClickPendingIntent(
|
||||||
|
R.id.widget_adaptive_button,
|
||||||
|
PendingIntent.getBroadcast(context, 2, adaptiveIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
)
|
||||||
|
views.setOnClickPendingIntent(
|
||||||
|
R.id.widget_anc_button,
|
||||||
|
PendingIntent.getBroadcast(context, 3, ancIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
)
|
||||||
|
ServiceManager.getService()?.updateNoiseControlWidget()
|
||||||
|
appWidgetManager.updateAppWidget(appWidgetIds, views)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
super.onReceive(context, intent)
|
||||||
|
if (intent.action == "ACTION_SET_ANC_MODE") {
|
||||||
|
val mode = intent.getIntExtra("ANC_MODE", 1)
|
||||||
|
ServiceManager.getService()?.setANCMode(mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:state_pressed="true">
|
|
||||||
<shape android:shape="rectangle">
|
|
||||||
<solid android:color="#121212" />
|
|
||||||
<corners android:radius="16dp" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<shape android:shape="rectangle">
|
|
||||||
<solid android:color="#404040" />
|
|
||||||
<corners android:radius="12dp" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
</selector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item>
|
|
||||||
<shape android:shape="oval">
|
|
||||||
<solid android:color="@android:color/transparent" />
|
|
||||||
<stroke
|
|
||||||
android:width="6dp"
|
|
||||||
android:color="#00ff00" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
</layer-list>
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#2C2A2F" />
|
||||||
|
<corners android:topLeftRadius="4dp" android:topRightRadius="24dp" android:bottomLeftRadius="4dp" android:bottomRightRadius="24dp" />
|
||||||
|
<padding android:bottom="8dp" android:left="8dp" android:right="8dp" android:top="8dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#2C2A2F" />
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
<padding android:bottom="8dp" android:left="8dp" android:right="8dp" android:top="8dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#2C2A2F" />
|
||||||
|
<corners android:topLeftRadius="24dp" android:topRightRadius="4dp" android:bottomLeftRadius="24dp" android:bottomRightRadius="4dp" />
|
||||||
|
<padding android:bottom="8dp" android:left="8dp" android:right="8dp" android:top="8dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_pressed="true">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#3D3B40" />
|
||||||
|
<corners android:topLeftRadius="4dp" android:topRightRadius="24dp" android:bottomLeftRadius="4dp" android:bottomRightRadius="24dp" />
|
||||||
|
<padding android:left="8dp" android:top="8dp" android:right="8dp" android:bottom="8dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#49474E" />
|
||||||
|
<corners android:topLeftRadius="4dp" android:topRightRadius="24dp" android:bottomLeftRadius="4dp" android:bottomRightRadius="24dp" />
|
||||||
|
<padding android:left="8dp" android:top="8dp" android:right="8dp" android:bottom="8dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_pressed="true">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#3D3B40" />
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
<padding android:left="8dp" android:top="8dp" android:right="8dp" android:bottom="8dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#49474E" />
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
<padding android:left="8dp" android:top="8dp" android:right="8dp" android:bottom="8dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_pressed="true">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#3D3B40" />
|
||||||
|
<corners android:topLeftRadius="24dp" android:topRightRadius="4dp" android:bottomLeftRadius="24dp" android:bottomRightRadius="4dp" />
|
||||||
|
<padding android:left="8dp" android:top="8dp" android:right="8dp" android:bottom="8dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#49474E" />
|
||||||
|
<corners android:topLeftRadius="24dp" android:topRightRadius="4dp" android:bottomLeftRadius="24dp" android:bottomRightRadius="4dp" />
|
||||||
|
<padding android:left="8dp" android:top="8dp" android:right="8dp" android:bottom="8dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
140
android/app/src/main/res/layout/noise_control_widget.xml
Normal file
140
android/app/src/main/res/layout/noise_control_widget.xml
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
style="@style/Widget.ALN.AppWidget.Container"
|
||||||
|
android:id="@+id/noise_control_widget"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:theme="@style/Theme.ALN.AppWidgetContainer">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@android:id/background"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/widget_off_button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginVertical="12dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="2dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/widget_button_shape_start"
|
||||||
|
android:clickable="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="52dp"
|
||||||
|
android:layout_height="52dp"
|
||||||
|
android:src="@drawable/noise_cancellation"
|
||||||
|
android:tint="@color/white" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:shadowColor="@color/black"
|
||||||
|
android:shadowRadius="12"
|
||||||
|
android:text="@string/off"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/widget_transparency_button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginVertical="12dp"
|
||||||
|
android:layout_marginEnd="2dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/widget_button_shape_middle"
|
||||||
|
android:clickable="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="52dp"
|
||||||
|
android:layout_height="52dp"
|
||||||
|
android:src="@drawable/transparency"
|
||||||
|
android:tint="@color/white" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:shadowColor="@color/black"
|
||||||
|
android:shadowRadius="12"
|
||||||
|
android:text="@string/transparency"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/widget_adaptive_button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginVertical="12dp"
|
||||||
|
android:layout_marginStart="2dp"
|
||||||
|
android:layout_marginEnd="2dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/widget_button_shape_middle"
|
||||||
|
android:clickable="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="52dp"
|
||||||
|
android:layout_height="52dp"
|
||||||
|
android:src="@drawable/adaptive"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:tint="@color/white" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:shadowColor="@color/black"
|
||||||
|
android:shadowRadius="12"
|
||||||
|
android:text="@string/adaptive"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/widget_anc_button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginVertical="12dp"
|
||||||
|
android:layout_marginStart="2dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/widget_button_shape_end"
|
||||||
|
android:clickable="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="52dp"
|
||||||
|
android:layout_height="52dp"
|
||||||
|
android:src="@drawable/noise_cancellation"
|
||||||
|
android:tint="@color/white" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:shadowColor="@color/black"
|
||||||
|
android:shadowRadius="12"
|
||||||
|
android:text="@string/noise_cancellation"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="12sp"
|
||||||
|
tools:ignore="NestedWeights" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
android:layout_height="28dp"
|
android:layout_height="28dp"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:layout_marginEnd="24dp"
|
android:layout_marginEnd="24dp"
|
||||||
android:background="@drawable/button_shape"
|
android:background="@drawable/popup_button_shape"
|
||||||
android:contentDescription="Close Button"
|
android:contentDescription="Close Button"
|
||||||
android:src="@drawable/close"
|
android:src="@drawable/close"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
@@ -106,4 +106,4 @@
|
|||||||
android:textColor="@color/popup_text"
|
android:textColor="@color/popup_text"
|
||||||
android:textSize="20sp" />
|
android:textSize="20sp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!--
|
<!--
|
||||||
Having themes.xml for night-v31 because of the priority order of the resource qualifiers.
|
Having themes.xml for night-v31 because of the priority order of the resource qualifiers.
|
||||||
-->
|
-->
|
||||||
<style name="Theme.ALN.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault.DayNight">
|
<style name="Theme.ALN.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault.DayNight">
|
||||||
<item name="appWidgetRadius">@android:dimen/system_app_widget_background_radius</item>
|
<item name="appWidgetRadius">@android:dimen/system_app_widget_background_radius</item>
|
||||||
<item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item>
|
<item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Widget.ALN.AppWidget.Container" parent="android:Widget">
|
<style name="Widget.ALN.AppWidget.Container" parent="android:Widget">
|
||||||
<item name="android:id">@android:id/background</item>
|
<item name="android:id">@android:id/background</item>
|
||||||
<item name="android:padding">?attr/appWidgetPadding</item>
|
<item name="android:padding">?attr/appWidgetPadding</item>
|
||||||
<item name="android:background">@drawable/app_widget_background</item>
|
<item name="android:background">@drawable/app_widget_background</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.ALN.AppWidget.InnerView" parent="android:Widget">
|
<style name="Widget.ALN.AppWidget.InnerView" parent="android:Widget">
|
||||||
<item name="android:padding">?attr/appWidgetPadding</item>
|
<item name="android:padding">?attr/appWidgetPadding</item>
|
||||||
<item name="android:background">@drawable/app_widget_inner_view_background</item>
|
<item name="android:background">@drawable/app_widget_inner_view_background</item>
|
||||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Widget.ALN.AppWidget.Container" parent="android:Widget">
|
<style name="Widget.ALN.AppWidget.Container" parent="android:Widget">
|
||||||
<item name="android:id">@android:id/background</item>
|
<item name="android:id">@android:id/background</item>
|
||||||
<item name="android:padding">?attr/appWidgetPadding</item>
|
<item name="android:padding">?attr/appWidgetPadding</item>
|
||||||
<item name="android:background">@drawable/app_widget_background</item>
|
<item name="android:background">@drawable/app_widget_background</item>
|
||||||
<item name="android:clipToOutline">true</item>
|
<item name="android:clipToOutline">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.ALN.AppWidget.InnerView" parent="android:Widget">
|
<style name="Widget.ALN.AppWidget.InnerView" parent="android:Widget">
|
||||||
<item name="android:padding">?attr/appWidgetPadding</item>
|
<item name="android:padding">?attr/appWidgetPadding</item>
|
||||||
<item name="android:background">@drawable/app_widget_inner_view_background</item>
|
<item name="android:background">@drawable/app_widget_inner_view_background</item>
|
||||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||||
<item name="android:clipToOutline">true</item>
|
<item name="android:clipToOutline">true</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!--
|
<!--
|
||||||
Having themes.xml for v31 variant because @android:dimen/system_app_widget_background_radius
|
Having themes.xml for v31 variant because @android:dimen/system_app_widget_background_radius
|
||||||
and @android:dimen/system_app_widget_internal_padding requires API level 31
|
and @android:dimen/system_app_widget_internal_padding requires API level 31
|
||||||
-->
|
-->
|
||||||
<style name="Theme.ALN.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault.DayNight">
|
<style name="Theme.ALN.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault.DayNight">
|
||||||
<item name="appWidgetRadius">@android:dimen/system_app_widget_background_radius</item>
|
<item name="appWidgetRadius">@android:dimen/system_app_widget_background_radius</item>
|
||||||
<item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item>
|
<item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<declare-styleable name="AppWidgetAttrs">
|
<declare-styleable name="AppWidgetAttrs">
|
||||||
<attr name="appWidgetPadding" format="dimension" />
|
<attr name="appWidgetPadding" format="dimension" />
|
||||||
<attr name="appWidgetInnerRadius" format="dimension" />
|
<attr name="appWidgetInnerRadius" format="dimension" />
|
||||||
<attr name="appWidgetRadius" format="dimension" />
|
<attr name="appWidgetRadius" format="dimension" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="black">#FF000000</color>
|
<color name="black">#FF000000</color>
|
||||||
<color name="white">#FFFFFFFF</color>
|
<color name="white">#FFFFFFFF</color>
|
||||||
<color name="popup_background">#FFFFFF</color>
|
<color name="popup_background">#FFFFFF</color>
|
||||||
<color name="popup_text">@color/black</color>
|
<color name="popup_text">@color/black</color>
|
||||||
<color name="widget_background">#87FFFFFF</color>
|
<color name="widget_background">#87FFFFFF</color>
|
||||||
<color name="widget_text">@color/black</color>
|
<color name="widget_text">@color/black</color>
|
||||||
</resources>
|
<color name="light_blue_50">#FFE1F5FE</color>
|
||||||
|
<color name="light_blue_200">#FF81D4FA</color>
|
||||||
|
<color name="light_blue_600">#FF039BE5</color>
|
||||||
|
<color name="light_blue_900">#FF01579B</color>
|
||||||
|
</resources>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Refer to App Widget Documentation for margin information
|
Refer to App Widget Documentation for margin information
|
||||||
http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout
|
http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout
|
||||||
-->
|
-->
|
||||||
<dimen name="widget_margin">0dp</dimen>
|
<dimen name="widget_margin">0dp</dimen>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,42 +1,45 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name" translatable="false">ALN</string>
|
<string name="app_name" translatable="false">ALN</string>
|
||||||
<string name="title_activity_custom_device" translatable="false">GATT Testing</string>
|
<string name="title_activity_custom_device" translatable="false">GATT Testing</string>
|
||||||
<string name="app_widget_description">See your AirPods battery status right from your home screen!</string>
|
<string name="app_widget_description">See your AirPods battery status right from your home screen!</string>
|
||||||
<string name="accessibility">Accessibility</string>
|
<string name="accessibility">Accessibility</string>
|
||||||
<string name="tone_volume">Tone Volume</string>
|
<string name="tone_volume">Tone Volume</string>
|
||||||
<string name="audio">Audio</string>
|
<string name="audio">Audio</string>
|
||||||
<string name="adaptive_audio">Adaptive Audio</string>
|
<string name="adaptive_audio">Adaptive Audio</string>
|
||||||
<string name="adaptive_audio_description">Adaptive audio dynamically responds to your environment and cancels or allows external noise. You can customize Adaptive Audio to allow more or less noise.</string>
|
<string name="adaptive_audio_description">Adaptive audio dynamically responds to your environment and cancels or allows external noise. You can customize Adaptive Audio to allow more or less noise.</string>
|
||||||
<string name="buds">Buds</string>
|
<string name="buds">Buds</string>
|
||||||
<string name="case_alt">Case</string>
|
<string name="case_alt">Case</string>
|
||||||
<string name="test">Test</string>
|
<string name="test">Test</string>
|
||||||
<string name="name">Name</string>
|
<string name="name">Name</string>
|
||||||
<string name="noise_control">Noise Control</string>
|
<string name="noise_control">Noise Control</string>
|
||||||
<string name="off">Off</string>
|
<string name="off">Off</string>
|
||||||
<string name="transparency">Transparency</string>
|
<string name="transparency">Transparency</string>
|
||||||
<string name="adaptive">Adaptive</string>
|
<string name="adaptive">Adaptive</string>
|
||||||
<string name="noise_cancellation">Noise Cancellation</string>
|
<string name="noise_cancellation">Noise Cancellation</string>
|
||||||
<string name="press_and_hold_airpods">Press and Hold AirPods</string>
|
<string name="press_and_hold_airpods">Press and Hold AirPods</string>
|
||||||
<string name="left">Left</string>
|
<string name="left">Left</string>
|
||||||
<string name="right">Right</string>
|
<string name="right">Right</string>
|
||||||
<string name="adjusts_volume">Adjusts the volume of media in response to your environment</string>
|
<string name="adjusts_volume">Adjusts the volume of media in response to your environment</string>
|
||||||
<string name="conversational_awareness">Conversational Awareness</string>
|
<string name="conversational_awareness">Conversational Awareness</string>
|
||||||
<string name="conversational_awareness_description">Lowers media volume and reduces background noise when you start speaking to other people.</string>
|
<string name="conversational_awareness_description">Lowers media volume and reduces background noise when you start speaking to other people.</string>
|
||||||
<string name="personalized_volume">Personalized Volume</string>
|
<string name="personalized_volume">Personalized Volume</string>
|
||||||
<string name="personalized_volume_description">Adjusts the volume of media in response to your environment.</string>
|
<string name="personalized_volume_description">Adjusts the volume of media in response to your environment.</string>
|
||||||
<string name="less_noise">Less Noise</string>
|
<string name="less_noise">Less Noise</string>
|
||||||
<string name="more_noise">More Noise</string>
|
<string name="more_noise">More Noise</string>
|
||||||
<string name="noise_cancellation_single_airpod">Noise Cancellation with Single AirPod</string>
|
<string name="noise_cancellation_single_airpod">Noise Cancellation with Single AirPod</string>
|
||||||
<string name="noise_cancellation_single_airpod_description">Allow AirPods to be put in noise cancellation mode when only one AirPods is in your ear.</string>
|
<string name="noise_cancellation_single_airpod_description">Allow AirPods to be put in noise cancellation mode when only one AirPods is in your ear.</string>
|
||||||
<string name="volume_control">Volume Control</string>
|
<string name="volume_control">Volume Control</string>
|
||||||
<string name="volume_control_description">Adjust the volume by swiping up or down on the sensor located on the AirPods Pro stem.</string>
|
<string name="volume_control_description">Adjust the volume by swiping up or down on the sensor located on the AirPods Pro stem.</string>
|
||||||
<string name="airpods_not_connected">AirPods not connected</string>
|
<string name="airpods_not_connected">AirPods not connected</string>
|
||||||
<string name="airpods_not_connected_description">Please connect your AirPods to access settings. If you\'re stuck here, then try reopening the app again after closing it from the recents.\n(DO NOT KILL THE APP!)</string>
|
<string name="airpods_not_connected_description">Please connect your AirPods to access settings. If you\'re stuck here, then try reopening the app again after closing it from the recents.\n(DO NOT KILL THE APP!)</string>
|
||||||
<string name="back">Back</string>
|
<string name="back">Back</string>
|
||||||
<string name="app_settings">App Settings</string>
|
<string name="app_settings">App Settings</string>
|
||||||
<string name="conversational_awareness_customization">Conversational Awareness</string>
|
<string name="conversational_awareness_customization">Conversational Awareness</string>
|
||||||
<string name="relative_conversational_awareness_volume">Relative volume</string>
|
<string name="relative_conversational_awareness_volume">Relative volume</string>
|
||||||
<string name="relative_conversational_awareness_volume_description">Reduces to a percentage of the current volume instead of the maximum volume.</string>
|
<string name="relative_conversational_awareness_volume_description">Reduces to a percentage of the current volume instead of the maximum volume.</string>
|
||||||
<string name="conversational_awareness_pause_music">Pause Music</string>
|
<string name="conversational_awareness_pause_music">Pause Music</string>
|
||||||
<string name="conversational_awareness_pause_music_description">When you start speaking, music will be paused.</string>
|
<string name="conversational_awareness_pause_music_description">When you start speaking, music will be paused.</string>
|
||||||
|
<string name="appwidget_text">EXAMPLE</string>
|
||||||
|
<string name="add_widget">Add widget</string>
|
||||||
|
<string name="noise_control_widget_description">Control Noise Control Mode directly from your Home Screen.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Widget.ALN.AppWidget.Container" parent="android:Widget">
|
<style name="Widget.ALN.AppWidget.Container" parent="android:Widget">
|
||||||
<item name="android:id">@android:id/background</item>
|
<item name="android:id">@android:id/background</item>
|
||||||
<item name="android:background">?android:attr/colorBackground</item>
|
<item name="android:background">?android:attr/colorBackground</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Widget.ALN.AppWidget.InnerView" parent="android:Widget">
|
<style name="Widget.ALN.AppWidget.InnerView" parent="android:Widget">
|
||||||
<item name="android:background">?android:attr/colorBackground</item>
|
<item name="android:background">?android:attr/colorBackground</item>
|
||||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Theme.ALN" parent="android:Theme.Material.Light.NoActionBar" />
|
<style name="Theme.ALN" parent="android:Theme.Material.Light.NoActionBar" />
|
||||||
|
|
||||||
<style name="Theme.ALN.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault">
|
<style name="Theme.ALN.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault">
|
||||||
<item name="appWidgetRadius">32dp</item>
|
<item name="appWidgetRadius">32dp</item>
|
||||||
<item name="appWidgetPadding">0dp</item>
|
<item name="appWidgetPadding">0dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.ALN.AppWidgetContainer" parent="Theme.ALN.AppWidgetContainerParent">
|
<style name="Theme.ALN.AppWidgetContainer" parent="Theme.ALN.AppWidgetContainerParent">
|
||||||
<item name="appWidgetPadding">0dp</item>
|
<item name="appWidgetPadding">0dp</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -11,6 +11,6 @@
|
|||||||
android:resizeMode="horizontal|vertical"
|
android:resizeMode="horizontal|vertical"
|
||||||
android:targetCellWidth="3"
|
android:targetCellWidth="3"
|
||||||
android:targetCellHeight="1"
|
android:targetCellHeight="1"
|
||||||
android:updatePeriodMillis="300000"
|
android:updatePeriodMillis="30000"
|
||||||
android:widgetCategory="home_screen|keyguard"
|
android:widgetCategory="home_screen|keyguard"
|
||||||
tools:ignore="UnusedAttribute" />
|
tools:ignore="UnusedAttribute" />
|
||||||
|
|||||||
16
android/app/src/main/res/xml/noise_control_widget_info.xml
Normal file
16
android/app/src/main/res/xml/noise_control_widget_info.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:description="@string/noise_control_widget_description"
|
||||||
|
android:initialKeyguardLayout="@layout/noise_control_widget"
|
||||||
|
android:initialLayout="@layout/noise_control_widget"
|
||||||
|
android:minWidth="180dp"
|
||||||
|
android:minHeight="40dp"
|
||||||
|
android:previewImage="@drawable/example_appwidget_preview"
|
||||||
|
android:previewLayout="@layout/noise_control_widget"
|
||||||
|
android:resizeMode="horizontal"
|
||||||
|
android:targetCellWidth="3"
|
||||||
|
android:targetCellHeight="1"
|
||||||
|
android:updatePeriodMillis="30000"
|
||||||
|
android:widgetCategory="home_screen|keyguard"
|
||||||
|
tools:ignore="UnusedAttribute" />
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 96 KiB |
BIN
android/imgs/transitions.mp4
Normal file
BIN
android/imgs/transitions.mp4
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 998 KiB After Width: | Height: | Size: 1.4 MiB |
Reference in New Issue
Block a user