mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
feat(aap): Also update system Bluetooth alias when renaming
After the AAP rename succeeds, try to update Android's per-device bond alias via the hidden BluetoothDevice.setAlias(String) method, so the new name also shows up in the system Bluetooth settings on this phone. Known failure mode on Android 12+: setAlias is gated behind a Companion Device Manager (CDM) association at the service layer, and raises 'does not have a CDM association with the Bluetooth Device' for third-party apps that don't hold one. In that case, a dedicated snackbar explains that the system rename didn't go through and suggests renaming manually in system settings or re-pairing. The AAP-level rename is always attempted first and is the load-bearing part; the system alias is a best-effort extra.
This commit is contained in:
@@ -307,6 +307,37 @@ class BluetoothManager2 @Inject constructor(
|
||||
private var _isNudgeAvailable: Boolean = true
|
||||
val isNudgeAvailable: Boolean get() = _isNudgeAvailable
|
||||
|
||||
/**
|
||||
* Set the Android-local alias for a bonded [device]. Updates what Android's system Bluetooth
|
||||
* settings display without touching the AirPods firmware itself. Uses reflection on the hidden
|
||||
* `setAlias(String)` method because the public API 30+ variant requires `BLUETOOTH_PRIVILEGED`,
|
||||
* which third-party apps cannot hold.
|
||||
*
|
||||
* Returns `true` on success, `false` if the call threw, was rejected, or returned `false`.
|
||||
*
|
||||
* Known failure mode on Android 12+: `SecurityException: does not have a CDM association with
|
||||
* the Bluetooth Device`. The hidden method was moved behind a Companion Device Manager (CDM)
|
||||
* permission check at the service layer — only apps that have explicitly requested and been
|
||||
* granted a CDM association for this specific device can rename it. CAPod does not currently
|
||||
* pursue a CDM association (that's a user-visible pairing flow), so setAlias is effectively
|
||||
* unavailable on modern Android and callers should be prepared to surface a user-facing
|
||||
* fallback when it returns false. See DeviceSettingsViewModel.setDeviceName.
|
||||
*/
|
||||
@android.annotation.SuppressLint("MissingPermission")
|
||||
fun setDeviceAlias(device: BluetoothDevice2, alias: String): Boolean {
|
||||
return try {
|
||||
val method = BluetoothDevice::class.java.getDeclaredMethod("setAlias", String::class.java)
|
||||
.apply { isAccessible = true }
|
||||
val result = method.invoke(device.internal, alias) as? Boolean ?: false
|
||||
log(TAG) { "setDeviceAlias(${device.address}, $alias) -> $result" }
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
val cause = (e as? java.lang.reflect.InvocationTargetException)?.cause ?: e
|
||||
log(TAG, WARN) { "setDeviceAlias(${device.address}, $alias) failed: $cause" }
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun nudgeConnection(device: BluetoothDevice2): Boolean = getBluetoothProfile().map { bluetoothProfile ->
|
||||
try {
|
||||
log(TAG) { "Nudging Android connection to $device" }
|
||||
|
||||
@@ -109,6 +109,12 @@ fun DeviceSettingsScreenHost(
|
||||
context.getString(R.string.device_settings_send_failed, event.message ?: ""),
|
||||
)
|
||||
}
|
||||
DeviceSettingsViewModel.Event.SystemRenameUnavailable -> {
|
||||
snackbarHostState.showSnackbar(
|
||||
context.getString(R.string.device_settings_rename_system_unavailable),
|
||||
duration = androidx.compose.material3.SnackbarDuration.Long,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-1
@@ -58,6 +58,7 @@ class DeviceSettingsViewModel @Inject constructor(
|
||||
sealed interface Event {
|
||||
data object OpenBluetoothSettings : Event
|
||||
data class SendFailed(val command: AapCommand, val message: String?) : Event
|
||||
data object SystemRenameUnavailable : Event
|
||||
}
|
||||
|
||||
val events = SingleEventFlow<Event>()
|
||||
@@ -191,7 +192,25 @@ class DeviceSettingsViewModel @Inject constructor(
|
||||
|
||||
fun setSleepDetection(enabled: Boolean) = sendProGated(AapCommand.SetSleepDetection(enabled))
|
||||
|
||||
fun setDeviceName(name: String) = send(AapCommand.SetDeviceName(name))
|
||||
fun setDeviceName(name: String) = launch {
|
||||
val address = targetAddress.value ?: return@launch
|
||||
sendInternal(AapCommand.SetDeviceName(name))
|
||||
|
||||
// Also try to update the Android-local bond alias so the new name shows in Android's
|
||||
// Bluetooth settings too. The AAP rename only changes what the AirPods themselves report;
|
||||
// Android's system display reads from the bond database and needs a separate update.
|
||||
val bonded = try {
|
||||
bluetoothManager.bondedDevices().first().firstOrNull { it.address == address }
|
||||
} catch (e: Exception) {
|
||||
log(TAG, WARN) { "bondedDevices() failed while renaming: ${e.message}" }
|
||||
null
|
||||
}
|
||||
val aliasOk = bonded?.let { bluetoothManager.setDeviceAlias(it, name) } ?: false
|
||||
if (!aliasOk) {
|
||||
log(TAG, WARN) { "System bond alias rename failed for $address — user must rename in system settings or re-pair" }
|
||||
events.emit(Event.SystemRenameUnavailable)
|
||||
}
|
||||
}
|
||||
|
||||
fun navToStemConfig() = launch {
|
||||
if (upgradeRepo.isPro()) {
|
||||
|
||||
@@ -496,6 +496,7 @@
|
||||
<string name="device_settings_rename_hint">Device name</string>
|
||||
<string name="device_settings_rename_confirm">Rename</string>
|
||||
<string name="device_settings_rename_invalid_ascii">Only ASCII characters are supported</string>
|
||||
<string name="device_settings_rename_system_unavailable">Android didn\'t let us rename the device here. To update the name in Bluetooth settings, rename it there or re-pair the device.</string>
|
||||
<string name="device_settings_send_failed">Could not apply setting: %1$s</string>
|
||||
<string name="device_settings_category_connections_label">Connected Devices</string>
|
||||
<string name="device_settings_connected_devices_description">Other devices currently connected to these AirPods</string>
|
||||
|
||||
Reference in New Issue
Block a user