package com.sptmobile.link import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import androidx.core.app.NotificationCompat // [impl->REQ-INBOUND-NOTIFS] /** * Android half of inbound notifications (DESIGN.md §Interlaced view * mechanics): every sending endpoint gets its OWN notification channel, * created on its first message — per-endpoint channels are the ruling, so * the user mutes a chatty agent in system settings without silencing the * rest. One drained entry = one notification; the id keys on msg-id so a * re-notify of the same message replaces rather than stacks. * * Without POST_NOTIFICATIONS (asked at app launch) `notify` is a system-side * no-op — the message still lands in host history, so it is late-surfaced in * the endpoint view, never lost. */ class InboundNotifier(private val context: Context) { private val manager = context.getSystemService(NotificationManager::class.java) fun notify(entries: List) { for (entry in entries) { manager.createNotificationChannel( NotificationChannel( CHANNEL_PREFIX + entry.from, entry.from, NotificationManager.IMPORTANCE_DEFAULT, ) ) manager.notify( TAG, (entry.msg_id ?: "${entry.from}@${entry.ts_ms}").hashCode(), NotificationCompat.Builder(context, CHANNEL_PREFIX + entry.from) .setSmallIcon(android.R.drawable.stat_notify_chat) .setContentTitle(entry.from) .setContentText(entry.body) .setStyle(NotificationCompat.BigTextStyle().bigText(entry.body)) .setWhen(entry.ts_ms) .setShowWhen(true) .setAutoCancel(true) .setContentIntent(tapIntent(entry.from)) .build(), ) } } /** * Tap deep-links into the SENDING endpoint's view: the launch intent * carries [EXTRA_OPEN_ENDPOINT] and MainActivity (singleTop) routes it. * Request code keys on the endpoint so per-endpoint intents don't * clobber each other; UPDATE_CURRENT keeps a reused one fresh. */ private fun tapIntent(endpoint: String): PendingIntent? = context.packageManager.getLaunchIntentForPackage(context.packageName)?.let { it.putExtra(EXTRA_OPEN_ENDPOINT, endpoint) PendingIntent.getActivity( context, endpoint.hashCode(), it, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT, ) } companion object { private const val TAG = "inbound" private const val CHANNEL_PREFIX = "inbound_" /** Intent extra: endpoint id whose view a notification tap opens. */ const val EXTRA_OPEN_ENDPOINT = "com.sptmobile.OPEN_ENDPOINT" } }