|
|
|
@ -14,7 +14,9 @@ import android.net.Uri |
|
|
|
import android.os.Build |
|
|
|
import android.os.Build |
|
|
|
import android.os.IBinder |
|
|
|
import android.os.IBinder |
|
|
|
import android.os.PowerManager |
|
|
|
import android.os.PowerManager |
|
|
|
|
|
|
|
import android.os.SystemClock |
|
|
|
import androidx.core.app.NotificationCompat |
|
|
|
import androidx.core.app.NotificationCompat |
|
|
|
|
|
|
|
import androidx.core.app.Person |
|
|
|
import androidx.core.app.ServiceCompat |
|
|
|
import androidx.core.app.ServiceCompat |
|
|
|
import androidx.core.content.ContextCompat |
|
|
|
import androidx.core.content.ContextCompat |
|
|
|
import com.utun.chat.ChatApplication |
|
|
|
import com.utun.chat.ChatApplication |
|
|
|
@ -27,7 +29,8 @@ import com.utun.chat.MainActivity |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
class CallService : Service() { |
|
|
|
class CallService : Service() { |
|
|
|
companion object { |
|
|
|
companion object { |
|
|
|
private const val CHANNEL = "ongoing_call" |
|
|
|
// У старого low-канала Android не позволяет повысить importance после создания. |
|
|
|
|
|
|
|
private const val CHANNEL = "ongoing_call_lockscreen" |
|
|
|
private const val NOTIFICATION_ID = 1002 |
|
|
|
private const val NOTIFICATION_ID = 1002 |
|
|
|
private const val EXTRA_ID = "call_id" |
|
|
|
private const val EXTRA_ID = "call_id" |
|
|
|
private const val ACTION_END = "com.utun.chat.action.END_CALL" |
|
|
|
private const val ACTION_END = "com.utun.chat.action.END_CALL" |
|
|
|
@ -53,10 +56,18 @@ class CallService : Service() { |
|
|
|
|
|
|
|
|
|
|
|
override fun onCreate() { |
|
|
|
override fun onCreate() { |
|
|
|
super.onCreate() |
|
|
|
super.onCreate() |
|
|
|
getSystemService(NotificationManager::class.java).createNotificationChannel( |
|
|
|
val manager = getSystemService(NotificationManager::class.java) |
|
|
|
NotificationChannel(CHANNEL, "Текущий звонок", NotificationManager.IMPORTANCE_LOW).apply { |
|
|
|
manager.createNotificationChannel( |
|
|
|
|
|
|
|
NotificationChannel(CHANNEL, "Текущий звонок", NotificationManager.IMPORTANCE_DEFAULT).apply { |
|
|
|
|
|
|
|
description = "Собеседник, длительность и завершение звонка на экране блокировки" |
|
|
|
setSound(null, null) |
|
|
|
setSound(null, null) |
|
|
|
|
|
|
|
enableVibration(false) |
|
|
|
|
|
|
|
lockscreenVisibility = Notification.VISIBILITY_PUBLIC |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
val channel = manager.getNotificationChannel(CHANNEL) |
|
|
|
|
|
|
|
LogManager.addLog("INFO", "CallService", |
|
|
|
|
|
|
|
"notification channel=$CHANNEL enabled=${manager.areNotificationsEnabled()} " + |
|
|
|
|
|
|
|
"importance=${channel.importance} lockscreenVisibility=${channel.lockscreenVisibility}") |
|
|
|
audio.onFailure = { id, detail -> CallController.audioFailed(id, detail) } |
|
|
|
audio.onFailure = { id, detail -> CallController.audioFailed(id, detail) } |
|
|
|
LogManager.addLog("INFO", "CallService", "created") |
|
|
|
LogManager.addLog("INFO", "CallService", "created") |
|
|
|
} |
|
|
|
} |
|
|
|
@ -122,6 +133,7 @@ class CallService : Service() { |
|
|
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL or micType |
|
|
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL or micType |
|
|
|
} else 0 |
|
|
|
} else 0 |
|
|
|
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification(), types) |
|
|
|
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification(), types) |
|
|
|
|
|
|
|
LogManager.addLog("INFO", "CallService", "foreground notification id=$callId phase=${CallController.state.value?.phase}") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fun startAudio(id: Long): Boolean { |
|
|
|
fun startAudio(id: Long): Boolean { |
|
|
|
@ -135,25 +147,43 @@ class CallService : Service() { |
|
|
|
|
|
|
|
|
|
|
|
fun updateNotification() { |
|
|
|
fun updateNotification() { |
|
|
|
getSystemService(NotificationManager::class.java).notify(NOTIFICATION_ID, notification()) |
|
|
|
getSystemService(NotificationManager::class.java).notify(NOTIFICATION_ID, notification()) |
|
|
|
|
|
|
|
LogManager.addLog("INFO", "CallService", "notification updated id=$callId phase=${CallController.state.value?.phase}") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun notification(): Notification { |
|
|
|
private fun notification(): Notification { |
|
|
|
val st = CallController.state.value |
|
|
|
val st = CallController.state.value?.takeIf { it.callId == callId } |
|
|
|
|
|
|
|
val active = st?.phase == CallPhase.ACTIVE |
|
|
|
|
|
|
|
val person = Person.Builder().setName(st?.peerName?.takeIf { it.isNotBlank() } ?: "vibechat") |
|
|
|
|
|
|
|
.setKey("node:${st?.peerNodeId}").build() |
|
|
|
val open = PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), |
|
|
|
val open = PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), |
|
|
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) |
|
|
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) |
|
|
|
val end = PendingIntent.getService(this, 0, |
|
|
|
val end = PendingIntent.getService(this, 0, |
|
|
|
Intent(this, CallService::class.java).setAction(ACTION_END) |
|
|
|
Intent(this, CallService::class.java).setAction(ACTION_END) |
|
|
|
.setData(Uri.parse("utun-call:$callId")).putExtra(EXTRA_ID, callId), |
|
|
|
.setData(Uri.parse("utun-call:$callId")).putExtra(EXTRA_ID, callId), |
|
|
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) |
|
|
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) |
|
|
|
return NotificationCompat.Builder(this, CHANNEL) |
|
|
|
val builder = NotificationCompat.Builder(this, CHANNEL) |
|
|
|
.setSmallIcon(android.R.drawable.ic_menu_call) |
|
|
|
.setSmallIcon(android.R.drawable.ic_menu_call) |
|
|
|
.setContentTitle(st?.peerName ?: "vibechat") |
|
|
|
.setContentTitle(person.name) |
|
|
|
.setContentText(if (st?.phase == CallPhase.ACTIVE) "Разговор" else "Установка звонка") |
|
|
|
.setContentText(when (st?.phase) { |
|
|
|
|
|
|
|
CallPhase.ACTIVE -> "Разговор" |
|
|
|
|
|
|
|
CallPhase.RINGING -> "Звоним…" |
|
|
|
|
|
|
|
else -> "Установка звонка" |
|
|
|
|
|
|
|
}) |
|
|
|
.setCategory(NotificationCompat.CATEGORY_CALL) |
|
|
|
.setCategory(NotificationCompat.CATEGORY_CALL) |
|
|
|
|
|
|
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) |
|
|
|
.setOngoing(true) |
|
|
|
.setOngoing(true) |
|
|
|
|
|
|
|
.setOnlyAlertOnce(true) |
|
|
|
|
|
|
|
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE) |
|
|
|
.setContentIntent(open) |
|
|
|
.setContentIntent(open) |
|
|
|
.addAction(0, "Завершить", end) |
|
|
|
.setStyle(NotificationCompat.CallStyle.forOngoingCall(person, end)) |
|
|
|
.build() |
|
|
|
.addPerson(person) |
|
|
|
|
|
|
|
.setShowWhen(active) |
|
|
|
|
|
|
|
.setUsesChronometer(active) |
|
|
|
|
|
|
|
if (active && st != null) { |
|
|
|
|
|
|
|
// Один источник начала разговора для UI и системного таймера; уведомление не обновляется каждую секунду. |
|
|
|
|
|
|
|
builder.setWhen(System.currentTimeMillis() - (SystemClock.elapsedRealtime() - st.startedAtElapsedRealtime)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return builder.build() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** Идемпотентное освобождение до остановки сервиса; выполняется в main thread. */ |
|
|
|
/** Идемпотентное освобождение до остановки сервиса; выполняется в main thread. */ |
|
|
|
|