Приватные уведомления на локскрине + откат биопривязки (требование владельца: звонки/push при блокировке)

Владелец: входящие звонки и push должны работать при заблокированном телефоне.
- Биопривязка app password ОТКЛОНЕНА (вариант А): блокировала приём звонка без
  разблокировки. Откачена из main (сохранена на ветке wip/biometric-binding).
  Пароль остаётся под Keystore AES-256-GCM (п.3) — работает в фоне.
- Приватность локскрина: сообщения и звонки → VISIBILITY_PRIVATE + generic
  public-версия ('Новое сообщение'/'Входящий звонок'); канал lockscreenVisibility=PRIVATE.
  Полный контент — после разблокировки. Push приходит plaintext, для показа/звонка
  пароль не нужен → звонки звонят и пуши приходят при блокировке.

Проверено сборкой: assembleRelease BUILD SUCCESSFUL, APK 53 МБ.
This commit is contained in:
b-dev-mobile
2026-07-07 21:22:24 +00:00
parent 6abb1b15c3
commit 5eb6efb36b
4 changed files with 41 additions and 3 deletions
@@ -241,7 +241,8 @@ object F7IncomingCallQueue {
.setStyle(callStyle)
.setCategory(NotificationCompat.CATEGORY_CALL)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setPublicVersion(genericCallPublic(context, intents.preview))
.setOngoing(true)
.setAutoCancel(false)
.setOnlyAlertOnce(true)
@@ -282,7 +283,8 @@ object F7IncomingCallQueue {
.setStyle(NotificationCompat.BigTextStyle().bigText(body))
.setCategory(NotificationCompat.CATEGORY_CALL)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setPublicVersion(genericCallPublic(context, intents.preview))
.setOngoing(true)
.setAutoCancel(false)
.setOnlyAlertOnce(true)
@@ -304,6 +306,21 @@ object F7IncomingCallQueue {
?: throw IllegalStateException("NotificationManager unavailable")
}
/**
* Обезличенная (public) версия уведомления звонка для локскрина: показывает лишь «Входящий
* звонок» без имени звонящего/комнаты (минимум информации, решение владельца). Полное имя —
* в полноэкранном UI звонка/после разблокировки. Full-screen intent работает независимо от visibility.
*/
private fun genericCallPublic(context: Context, preview: PendingIntent): android.app.Notification =
NotificationCompat.Builder(context, F7NotificationChannels.CALLS)
.setSmallIcon(android.R.drawable.stat_sys_phone_call)
.setContentTitle("F7cloud")
.setContentText("Входящий звонок")
.setCategory(NotificationCompat.CATEGORY_CALL)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setContentIntent(preview)
.build()
private fun resolveJoinUrl(context: Context, call: PendingCall): String? {
val raw = call.acceptUrl?.takeIf { it.isNotBlank() }
?: AuthStore(context).load()?.let { session ->
@@ -48,7 +48,10 @@ object F7NotificationChannels {
setSound(sound, soundAttrs)
}
setShowBadge(true)
lockscreenVisibility = android.app.Notification.VISIBILITY_PUBLIC
// Приватно на локскрине: система покажет public-версию (generic), а не полный
// контент (минимум информации, решение владельца). Public-версия задаётся
// per-notification в F7PushNotificationHelper / F7IncomingCallQueue.
lockscreenVisibility = android.app.Notification.VISIBILITY_PRIVATE
},
)
}
@@ -49,6 +49,21 @@ object F7PushNotificationHelper {
}
val iconRes = context.applicationInfo.icon.takeIf { it != 0 }
?: android.R.drawable.stat_notify_chat
// На локскрине — ОБЕЗЛИЧЕННАЯ версия (минимум информации, решение владельца): отправитель
// и текст не утекают на заблокированном экране. Полный контент — после разблокировки.
val genericText = when (type) {
"chat", "room", "message" -> "Новое сообщение"
"call" -> "Входящий звонок"
else -> "Новое уведомление"
}
val publicVersion = NotificationCompat.Builder(context, channel)
.setSmallIcon(iconRes)
.setContentTitle("F7cloud")
.setContentText(genericText)
.setAutoCancel(true)
.setPriority(priority)
.setContentIntent(pending)
.build()
val notification = NotificationCompat.Builder(context, channel)
.setSmallIcon(iconRes)
.setContentTitle(title)
@@ -56,6 +71,8 @@ object F7PushNotificationHelper {
.setStyle(NotificationCompat.BigTextStyle().bigText(body))
.setAutoCancel(true)
.setPriority(priority)
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setPublicVersion(publicVersion)
.setContentIntent(pending)
.build()
manager.notify((System.currentTimeMillis() % Int.MAX_VALUE).toInt(), notification)