feat: Foreground Service + Detachment 개선 + Placement 가이드 모드
Foreground Service (BleForegroundService.kt): - BLE 연결 시 자동 시작, 해제 시 자동 중단 - PARTIAL_WAKE_LOCK (최대 4시간) - Notification: "VesiScan - BLE 기기 연결 중..." - connectedDevice foregroundServiceType - Doze 모드에서도 BLE 통신 유지 - keepScreenOn도 유지 (추가 안전장치) Detachment 개선: - THR_MEAN/STD 100→30 (완전 탈착만 감지, 거리 약화 무시) - 6채널 모두 수신됐을 때만 체크 (BLE 거리 문제와 구분) - Placement: if(false) 제거 → 활성화 + "센서가 부착되지 않았습니다" - Monitoring: 정상 복귀 시 LED OFF Placement 가이드 모드: - Simple (기본): 3+채널 → 바로 좌우 (기존) - Boundary: 올라갔다 내려오기 패턴 (경계 감지 → 최적점 복귀) - 설정 톱니바퀴에 Simple/Boundary 스위치 BLE 로그: - 역순 표시 (최신이 위) — 스크롤 위치 유지 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
package com.example.medilightv2android.services
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.example.medilightv2android.MainActivity
|
||||
import com.example.medilightv2android.R
|
||||
|
||||
/**
|
||||
* BLE 연결 유지를 위한 Foreground Service.
|
||||
*
|
||||
* 화면이 꺼져도 Android Doze 모드에서 앱을 유지하여
|
||||
* BLE 통신(TX/RX), watchdog, Auto 측정이 중단되지 않도록 한다.
|
||||
*/
|
||||
class BleForegroundService : Service() {
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID = "vesiscan_ble_channel"
|
||||
const val NOTIFICATION_ID = 1001
|
||||
const val ACTION_START = "com.example.medilightv2android.BLE_START"
|
||||
const val ACTION_STOP = "com.example.medilightv2android.BLE_STOP"
|
||||
|
||||
fun start(context: Context) {
|
||||
val intent = Intent(context, BleForegroundService::class.java).apply {
|
||||
action = ACTION_START
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
fun stop(context: Context) {
|
||||
val intent = Intent(context, BleForegroundService::class.java).apply {
|
||||
action = ACTION_STOP
|
||||
}
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private var wakeLock: PowerManager.WakeLock? = null
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
createNotificationChannel()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
when (intent?.action) {
|
||||
ACTION_STOP -> {
|
||||
releaseWakeLock()
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
else -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(NOTIFICATION_ID, buildNotification(),
|
||||
android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE)
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, buildNotification())
|
||||
}
|
||||
acquireWakeLock()
|
||||
}
|
||||
}
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
releaseWakeLock()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
"VesiScan BLE Connection",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
description = "BLE 기기 연결 유지"
|
||||
setShowBadge(false)
|
||||
}
|
||||
val nm = getSystemService(NotificationManager::class.java)
|
||||
nm.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildNotification(): Notification {
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this, 0,
|
||||
Intent(this, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
},
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
return NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle("VesiScan")
|
||||
.setContentText("BLE 기기 연결 중...")
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||
.setOngoing(true)
|
||||
.setContentIntent(pendingIntent)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun acquireWakeLock() {
|
||||
if (wakeLock == null) {
|
||||
val pm = getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
wakeLock = pm.newWakeLock(
|
||||
PowerManager.PARTIAL_WAKE_LOCK,
|
||||
"VesiScan::BleWakeLock"
|
||||
).apply {
|
||||
acquire(4 * 60 * 60 * 1000L) // 최대 4시간
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun releaseWakeLock() {
|
||||
wakeLock?.let {
|
||||
if (it.isHeld) it.release()
|
||||
}
|
||||
wakeLock = null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user