ble: 다중 기기 페어링 영속화 (KnownDeviceStore)

기존 단일 last_device_address/name 키를 JSON 리스트로 확장.
여러 각도 변형 기기를 등록해두고 PIN 재입력 없이 골라 연결 가능.

KnownDeviceStore (services/):
- getAll / add / remove / mostRecent
- 구 단일 키 자동 마이그레이션 (read 시 흡수, 새 add 시 호환 유지)

BleManager:
- 연결 성공 시 KnownDeviceStore.add 호출 (단일 키 직접 쓰기 제거)
- autoConnectLastDevice: mostRecent() 사용
- disconnectAndUnbond: known list 에서도 제거
- connectByAddress(addr,name) 신규 — 스캔 결과 없어도 OS bond 가
  있으면 바로 연결. KnownDeviceStore 에서 골라 호출.
- unbondAndRemoveAddress(addr) 신규 — 연결 안 된 기기도 페어링
  해제 가능 (removeBond + KnownDeviceStore.remove)
This commit is contained in:
2026-06-11 15:01:20 +09:00
parent 7fa18ec05a
commit b782db16b3
2 changed files with 147 additions and 13 deletions
@@ -246,14 +246,58 @@ class BleManager private constructor(private val context: Context) {
handler.postDelayed(timeout, 10000)
}
/**
* 특정 주소의 known device 페어링 해제 — 현재 연결된 기기가 아니어도 사용 가능.
* 연결된 상태라면 일반 disconnectAndUnbond() 를 사용.
*/
fun unbondAndRemoveAddress(address: String) {
// 현재 연결된 기기를 해제하려는 경우
if (bluetoothGatt?.device?.address == address) {
disconnectAndUnbond()
return
}
// 연결 안 된 known device — OS bond 만 끊고 list 에서 제거
val adapter = bluetoothAdapter ?: return
val device = runCatching { adapter.getRemoteDevice(address) }.getOrNull()
device?.let { dev ->
try {
val method = dev.javaClass.getMethod("removeBond")
method.invoke(dev)
logd { "removeBond called for ${dev.address}" }
} catch (e: Exception) {
logw { "removeBond failed: ${e.message}" }
}
}
com.example.medilightv2android.services.KnownDeviceStore.remove(context, address)
debugLogger.info("DEVICE_UNREGISTERED $address")
}
/**
* 저장된 기기 주소로 직접 연결. 스캔 결과에 안 잡혀도 OS bond 가 있으면 바로 됨.
* KnownDeviceStore 에서 골라 호출.
*/
fun connectByAddress(address: String, name: String) {
val adapter = bluetoothAdapter ?: run {
connectionError.value = "Bluetooth not available"
return
}
val device = try {
adapter.getRemoteDevice(address)
} catch (e: IllegalArgumentException) {
connectionError.value = "Invalid device address"
return
}
connect(BleDevice(device = device, rssi = 0))
// discoveredDevices 에 없을 수 있으므로 name 을 별도로 저장 (기존 connect 가 onConnected 시점에 name 설정)
connectedDeviceName.value = name
}
/** 저장된 마지막 기기에 자동 연결 시도 */
fun autoConnectLastDevice() {
if (isConnected.value) return
val prefs = context.getSharedPreferences("medilight_prefs", Context.MODE_PRIVATE)
val address = prefs.getString("last_device_address", null) ?: return
val name = prefs.getString("last_device_name", null) ?: return
debugLogger.info("AUTO_CONNECT attempting $name ($address)")
lastConnectedAddress = address
val recent = com.example.medilightv2android.services.KnownDeviceStore.mostRecent(context) ?: return
debugLogger.info("AUTO_CONNECT attempting ${recent.name} (${recent.address})")
lastConnectedAddress = recent.address
scheduleAutoReconnect()
}
@@ -308,10 +352,11 @@ class BleManager private constructor(private val context: Context) {
}
}
// 저장된 기기 정보 삭제
// 저장된 기기 정보 삭제 — known device list 에서도 제거
try {
val prefs = context.getSharedPreferences("medilight_prefs", Context.MODE_PRIVATE)
prefs.edit().remove("last_device_address").remove("last_device_name").apply()
device?.let { dev ->
com.example.medilightv2android.services.KnownDeviceStore.remove(context, dev.address)
}
} catch (_: Exception) {}
debugLogger.info("DEVICE_UNREGISTERED")
@@ -809,11 +854,9 @@ class BleManager private constructor(private val context: Context) {
debugLogger.connected(connectedDeviceName.value, gatt.device.address)
try {
val prefs = context.getSharedPreferences("medilight_prefs", Context.MODE_PRIVATE)
prefs.edit()
.putString("last_device_address", gatt.device.address)
.putString("last_device_name", connectedDeviceName.value)
.apply()
com.example.medilightv2android.services.KnownDeviceStore.add(
context, gatt.device.address, connectedDeviceName.value
)
} catch (_: Exception) {}
// MTU 247 요청 (reb: 208B 패킷 드롭 방지)