NotificationsManager.kt 22.1 KB
Newer Older
Christophe Deschamps's avatar
Christophe Deschamps committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
 * Copyright (c) 2010-2020 Belledonne Communications SARL.
 *
 * This file is part of linphone-android
 * (see https://www.linphone.org).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.lindoor.notifications

import android.app.Notification
Christophe Deschamps's avatar
Christophe Deschamps committed
23
import android.app.PendingIntent
Christophe Deschamps's avatar
Christophe Deschamps committed
24
import android.content.Context
Christophe Deschamps's avatar
Christophe Deschamps committed
25
import android.content.Intent
Christophe Deschamps's avatar
Christophe Deschamps committed
26 27
import android.graphics.Bitmap
import android.net.Uri
28
import android.view.TextureView
Christophe Deschamps's avatar
Christophe Deschamps committed
29
import android.widget.RemoteViews
Christophe Deschamps's avatar
Christophe Deschamps committed
30 31 32 33
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.navigation.NavDeepLinkBuilder
Christophe Deschamps's avatar
Christophe Deschamps committed
34 35
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.AppWidgetTarget
36 37 38 39 40
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.lindoor.LindoorApplication
Christophe Deschamps's avatar
Christophe Deschamps committed
41 42 43 44
import org.lindoor.LindoorApplication.Companion.coreContext
import org.lindoor.LindoorApplication.Companion.corePreferences
import org.lindoor.MainActivity
import org.lindoor.R
45
import org.lindoor.customisation.DeviceTypes
Christophe Deschamps's avatar
Christophe Deschamps committed
46
import org.lindoor.customisation.Texts
Christophe Deschamps's avatar
Christophe Deschamps committed
47
import org.lindoor.customisation.Theme
Christophe Deschamps's avatar
Christophe Deschamps committed
48
import org.lindoor.linphonecore.CoreService
49 50
import org.lindoor.linphonecore.extensions.extendedAcceptEarlyMedia
import org.lindoor.linphonecore.extensions.historyEvent
Christophe Deschamps's avatar
Christophe Deschamps committed
51
import org.lindoor.linphonecore.extensions.missedCount
Christophe Deschamps's avatar
Christophe Deschamps committed
52 53 54 55 56
import org.lindoor.store.DeviceStore
import org.lindoor.ui.call.CallInProgressActivity
import org.lindoor.ui.call.CallIncomingActivity
import org.lindoor.ui.call.CallOutgoingActivity
import org.lindoor.utils.extensions.existsAndIsNotEmpty
Christophe Deschamps's avatar
Christophe Deschamps committed
57 58
import org.linphone.compatibility.Compatibility
import org.linphone.core.Call
59
import org.linphone.core.CallListenerStub
Christophe Deschamps's avatar
Christophe Deschamps committed
60 61 62
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
import org.linphone.core.tools.Log
63
import java.io.File
64 65 66
import java.util.*
import kotlin.collections.HashMap
import kotlin.concurrent.fixedRateTimer
Christophe Deschamps's avatar
Christophe Deschamps committed
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107


private class Notifiable(val notificationId: Int) {
    val messages: ArrayList<NotifiableMessage> = arrayListOf()

    var isGroup: Boolean = false
    var groupTitle: String? = null
    var localIdentity: String? = null
    var myself: String? = null
}

private class NotifiableMessage(
    var message: String,
    val sender: String,
    val time: Long,
    val senderAvatar: Bitmap? = null,
    var filePath: Uri? = null,
    var fileMime: String? = null
)

class NotificationsManager(private val context: Context) {
    companion object {
        const val INTENT_NOTIF_ID = "NOTIFICATION_ID"
        const val INTENT_HANGUP_CALL_NOTIF_ACTION = "org.lindoor.HANGUP_CALL_ACTION"
        const val INTENT_ANSWER_CALL_NOTIF_ACTION = "org.lindoor.ANSWER_CALL_ACTION"

        private const val SERVICE_NOTIF_ID = 1
        private const val MISSED_CALLS_NOTIF_ID = 2
    }


    private val notificationManager: NotificationManagerCompat by lazy {
        NotificationManagerCompat.from(context)
    }
    private val callNotificationsMap: HashMap<String, Notifiable> = HashMap()

    private var lastNotificationId: Int = 5
    private var currentForegroundServiceNotificationId: Int = 0
    private var serviceNotification: Notification? = null
    var service: CoreService? = null

108
    private val coreListener: CoreListenerStub = object : CoreListenerStub() {
Christophe Deschamps's avatar
Christophe Deschamps committed
109 110 111 112 113 114 115 116 117 118
        override fun onCallStateChanged(
            core: Core?,
            call: Call?,
            state: Call.State?,
            message: String?
        ) {
            if (call == null) return
            Log.i("[Notifications Manager] Call state changed [$state]")

            when (state) {
Christophe Deschamps's avatar
Christophe Deschamps committed
119 120 121
                Call.State.IncomingEarlyMedia, Call.State.IncomingReceived -> displayIncomingCallNotification(
                    call
                )
Christophe Deschamps's avatar
Christophe Deschamps committed
122
                Call.State.End, Call.State.Error -> dismissCallNotification(call)
Christophe Deschamps's avatar
Christophe Deschamps committed
123 124
                Call.State.Released -> {
                    if (call.callLog?.status == Call.Status.Missed) {
Christophe Deschamps's avatar
Christophe Deschamps committed
125
                        displayMissedCallNotification(call)
Christophe Deschamps's avatar
Christophe Deschamps committed
126 127
                    }
                }
Christophe Deschamps's avatar
Christophe Deschamps committed
128
                else -> displayCallNotification(call)
Christophe Deschamps's avatar
Christophe Deschamps committed
129 130
            }
        }
131 132
    }

Christophe Deschamps's avatar
Christophe Deschamps committed
133

134
    var fileLenght = 0L
Christophe Deschamps's avatar
Christophe Deschamps committed
135 136
    var incomingCallSnapshotTimer: Timer? = null
    private lateinit var notificationBuilder: NotificationCompat.Builder
137 138 139 140 141
    private var callListener = object : CallListenerStub() {
        override fun onStateChanged(call: Call?, cstate: Call.State?, message: String?) {
            if (cstate != Call.State.IncomingEarlyMedia || cstate != Call.State.IncomingReceived)
                incomingCallSnapshotTimer?.cancel()
        }
Christophe Deschamps's avatar
Christophe Deschamps committed
142

143 144 145
        override fun onNextVideoFrameDecoded(call: Call?) {
            if (call != null) {
                call.takeVideoSnapshot(call.callLog.historyEvent().mediaThumbnail.absolutePath)
Christophe Deschamps's avatar
Christophe Deschamps committed
146 147 148 149 150
                GlobalScope.launch(context = Dispatchers.Main) {
                    delay(1000)
                    call.callLog.historyEvent().mediaThumbnail.also {
                        if (it.existsAndIsNotEmpty() && it.length() != fileLenght && !LindoorApplication.someActivityRunning) {
                            fileLenght = it.length()
151
                            val remoteView =
Christophe Deschamps's avatar
Christophe Deschamps committed
152 153 154 155
                                fillIncomingRemoteViewsForCall(call, true)
                            val awt = AppWidgetTarget(
                                context.applicationContext,
                                R.id.caller_picture,
156
                                remoteView,
Christophe Deschamps's avatar
Christophe Deschamps committed
157 158 159
                                0
                            )
                            Glide.with(context.applicationContext).asBitmap().load(it).into(awt)
160
                            notificationBuilder.setCustomBigContentView(remoteView)
161
                            notificationBuilder.setCustomContentView(remoteView)
Christophe Deschamps's avatar
Christophe Deschamps committed
162 163 164
                            val notification = notificationBuilder.build()
                            if (!LindoorApplication.someActivityRunning && call.state == Call.State.IncomingEarlyMedia)
                                notify(getNotifiableForCall(call).notificationId, notification)
165 166
                        }
                    }
Christophe Deschamps's avatar
Christophe Deschamps committed
167
                }
168 169
            }
        }
Christophe Deschamps's avatar
Christophe Deschamps committed
170 171 172 173 174 175 176 177 178
    }

    init {
        notificationManager.cancelAll()

        Compatibility.createNotificationChannels(context, notificationManager)
    }

    fun onCoreReady() {
179
        coreContext.core.addListener(coreListener)
Christophe Deschamps's avatar
Christophe Deschamps committed
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
    }

    fun destroy() {
        // Don't use cancelAll to keep message notifications !
        // When a message is received by a push, it will create a CoreService
        // but it might be getting killed quite quickly after that
        // causing the notification to be missed by the user...
        Log.i("[Notifications Manager] Getting destroyed, clearing foreground Service & call notifications")

        if (currentForegroundServiceNotificationId > 0) {
            notificationManager.cancel(currentForegroundServiceNotificationId)
        }

        for (notifiable in callNotificationsMap.values) {
            notificationManager.cancel(notifiable.notificationId)
        }

        stopForegroundNotification()
198
        coreContext.core.removeListener(coreListener)
Christophe Deschamps's avatar
Christophe Deschamps committed
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
    }

    private fun notify(id: Int, notification: Notification) {
        Log.i("[Notifications Manager] Notifying $id")
        notificationManager.notify(id, notification)
    }

    fun cancel(id: Int) {
        Log.i("[Notifications Manager] Canceling $id")
        notificationManager.cancel(id)
    }

    fun getSipUriForCallNotificationId(notificationId: Int): String? {
        for (address in callNotificationsMap.keys) {
            if (callNotificationsMap[address]?.notificationId == notificationId) {
                return address
            }
        }
        return null
    }

    /* Service related */

    fun startForeground() {
        val coreService = service
        if (coreService != null) {
            startForeground(coreService, useAutoStartDescription = false)
        } else {
            Log.e("[Notifications Manager] Can't start service as foreground, no service!")
        }
    }

    fun startCallForeground(coreService: CoreService) {
        service = coreService
        when {
            currentForegroundServiceNotificationId != 0 -> {
                Log.e("[Notifications Manager] There is already a foreground service notification")
            }
            coreContext.core.callsNb > 0 -> {
                // When this method will be called, we won't have any notification yet
                val call = coreContext.core.currentCall ?: coreContext.core.calls[0]
                when (call.state) {
                    Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> {
242
                        //displayIncomingCallNotification(call, true)
Christophe Deschamps's avatar
Christophe Deschamps committed
243
                    }
Christophe Deschamps's avatar
Christophe Deschamps committed
244
                    else -> displayCallNotification(call, true)
Christophe Deschamps's avatar
Christophe Deschamps committed
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
                }
            }
        }
    }

    fun startForeground(coreService: CoreService, useAutoStartDescription: Boolean = true) {
        Log.i("[Notifications Manager] Starting Service as foreground")
        if (serviceNotification == null) {
            createServiceNotification(useAutoStartDescription)
        }
        currentForegroundServiceNotificationId = SERVICE_NOTIF_ID
        coreService.startForeground(currentForegroundServiceNotificationId, serviceNotification)
        service = coreService
    }

    private fun startForeground(notificationId: Int, callNotification: Notification) {
        if (currentForegroundServiceNotificationId == 0 && service != null) {
            Log.i("[Notifications Manager] Starting Service as foreground using call notification")
            currentForegroundServiceNotificationId = notificationId
            service?.startForeground(currentForegroundServiceNotificationId, callNotification)
        }
Christophe Deschamps's avatar
Christophe Deschamps committed
266
    }
Christophe Deschamps's avatar
Christophe Deschamps committed
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295

    private fun stopForegroundNotification() {
        if (service != null) {
            Log.i("[Notifications Manager] Stopping Service as foreground")
            service?.stopForeground(true)
            currentForegroundServiceNotificationId = 0
        }
    }

    fun stopForegroundNotificationIfPossible() {
        if (service != null && currentForegroundServiceNotificationId == SERVICE_NOTIF_ID && !corePreferences.keepServiceAlive) {
            stopForegroundNotification()
        }
    }

    fun stopCallForeground() {
        if (service != null && currentForegroundServiceNotificationId != SERVICE_NOTIF_ID && !corePreferences.keepServiceAlive) {
            stopForegroundNotification()
        }
    }


    private fun createServiceNotification(useAutoStartDescription: Boolean = false) {
        val pendingIntent = NavDeepLinkBuilder(context)
            .setComponentName(MainActivity::class.java)
            .setGraph(R.navigation.fragments_graph)
            .setDestination(R.id.navigation_devices)
            .createPendingIntent()

Christophe Deschamps's avatar
Christophe Deschamps committed
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
        serviceNotification =
            NotificationCompat.Builder(context, Texts.get("notification_channel_service_id"))
                .setContentTitle(Texts.get("service_name"))
                .setContentText(
                    if (useAutoStartDescription) Texts.get("service_auto_start_description") else Texts.get(
                        "service_description"
                    )
                )
                .setSmallIcon(R.drawable.ic_lindoor_icon)
                .setContentIntent(pendingIntent)
                .setCategory(Notification.CATEGORY_SERVICE)
                .setVisibility(NotificationCompat.VISIBILITY_SECRET)
                .setWhen(System.currentTimeMillis())
                .setShowWhen(true)
                .setOngoing(true)
                .setColor(ContextCompat.getColor(context, R.color.color_a))
                .build()
Christophe Deschamps's avatar
Christophe Deschamps committed
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
    }

    /* Call related */

    private fun getNotifiableForCall(call: Call): Notifiable {
        val address = call.remoteAddress.asStringUriOnly()
        var notifiable: Notifiable? = callNotificationsMap[address]
        if (notifiable == null) {
            notifiable = Notifiable(lastNotificationId)
            lastNotificationId += 1
            callNotificationsMap[address] = notifiable
        }
        return notifiable
    }

Christophe Deschamps's avatar
Christophe Deschamps committed
328

Christophe Deschamps's avatar
Christophe Deschamps committed
329
    private fun fillIncomingRemoteViewsForCall(call: Call, hasSnapShot: Boolean): RemoteViews {
330 331 332
        val device =  DeviceStore.findDeviceByAddress(call.remoteAddress)
        val displayName = device?.name ?: call.remoteAddress.username
        val remoteView =
333
            if (hasSnapShot)
Christophe Deschamps's avatar
Christophe Deschamps committed
334 335
                RemoteViews(
                    context.packageName,
Christophe Deschamps's avatar
Build 6  
Christophe Deschamps committed
336
                    R.layout.call_incoming_notification_content_with_snapshot
Christophe Deschamps's avatar
Christophe Deschamps committed
337
                )
338
            else
Christophe Deschamps's avatar
Build 6  
Christophe Deschamps committed
339
                RemoteViews(context.packageName, R.layout.call_incoming_notification_content)
340

341 342
        remoteView.setTextViewText(R.id.caller, displayName)
        remoteView.setTextViewText(
Christophe Deschamps's avatar
Christophe Deschamps committed
343 344 345
            R.id.incoming_call_info,
            Texts.get("notif_incoming_call_title")
        )
346

347 348 349 350 351 352 353 354 355 356
        val awt = AppWidgetTarget(
            context.applicationContext,
            R.id.device_type,
            remoteView,
            if (hasSnapShot) 1 else 2
        )

        Glide.with(context.applicationContext).asBitmap().load(device?.let { device ->
            device.typeIconAsBitmap?.let {
                it
357
            }
358
        } ?: DeviceTypes.defaultTypeIconAsBitmap).into(awt)
359 360 361


        return remoteView
362 363
    }

Christophe Deschamps's avatar
Christophe Deschamps committed
364

Christophe Deschamps's avatar
Christophe Deschamps committed
365
    private fun displayIncomingCallNotification(call: Call, useAsForeground: Boolean = false) {
Christophe Deschamps's avatar
Christophe Deschamps committed
366

Christophe Deschamps's avatar
Christophe Deschamps committed
367
        val notifiable = getNotifiableForCall(call)
Christophe Deschamps's avatar
Christophe Deschamps committed
368 369
        val displayName =
            DeviceStore.findDeviceByAddress(call.remoteAddress)?.name ?: call.remoteAddress.username
Christophe Deschamps's avatar
Christophe Deschamps committed
370

Christophe Deschamps's avatar
Christophe Deschamps committed
371
        val incomingCallNotificationIntent = Intent(context, CallIncomingActivity::class.java)
Christophe Deschamps's avatar
Christophe Deschamps committed
372
        incomingCallNotificationIntent.putExtra("callId", call.callLog.callId)
Christophe Deschamps's avatar
Christophe Deschamps committed
373
        incomingCallNotificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
Christophe Deschamps's avatar
Christophe Deschamps committed
374 375 376 377 378 379
        val pendingIntent = PendingIntent.getActivity(
            context,
            0,
            incomingCallNotificationIntent,
            PendingIntent.FLAG_UPDATE_CURRENT
        )
Christophe Deschamps's avatar
Christophe Deschamps committed
380 381


Christophe Deschamps's avatar
Christophe Deschamps committed
382 383 384 385 386 387 388 389
        notificationBuilder =
            NotificationCompat.Builder(context, Texts.get("notification_channel_incoming_call_id"))
                .setStyle(NotificationCompat.DecoratedCustomViewStyle())
                .setSmallIcon(R.drawable.notification_phone)
                .setContentTitle(displayName)
                .setContentText(Texts.get("notif_incoming_call_title"))
                .setContentIntent(pendingIntent)
                .setCategory(NotificationCompat.CATEGORY_CALL)
Christophe Deschamps's avatar
Build 6  
Christophe Deschamps committed
390
                .setPriority(NotificationCompat.PRIORITY_MAX)
Christophe Deschamps's avatar
Christophe Deschamps committed
391 392 393 394 395 396 397 398 399
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                .setWhen(System.currentTimeMillis())
                .setAutoCancel(false)
                .setShowWhen(true)
                .setOngoing(true)
                .setColor(ContextCompat.getColor(context, R.color.color_a))
                .setFullScreenIntent(pendingIntent, true)
                .addAction(getCallDeclineAction(notifiable.notificationId))
                .addAction(getCallAnswerAction(notifiable.notificationId))
Christophe Deschamps's avatar
Build 6  
Christophe Deschamps committed
400
                .setCustomContentView(fillIncomingRemoteViewsForCall(call, false))
401
                .setCustomBigContentView(fillIncomingRemoteViewsForCall(call, false))
402 403 404 405 406

        val notification = notificationBuilder.build()

        if (!LindoorApplication.someActivityRunning)
            notify(notifiable.notificationId, notification)
Christophe Deschamps's avatar
Christophe Deschamps committed
407

Christophe Deschamps's avatar
Christophe Deschamps committed
408 409 410 411

        if (useAsForeground) {
            startForeground(notifiable.notificationId, notification)
        }
412 413 414 415 416 417 418 419 420

        val fakeTextureView = TextureView(LindoorApplication.instance.applicationContext)
        coreContext.core.nativeVideoWindowId = fakeTextureView
        call.addListener(callListener)
        call.extendedAcceptEarlyMedia()
        scheduleSnapShots(call)
    }


Christophe Deschamps's avatar
Christophe Deschamps committed
421 422
    fun scheduleSnapShots(call: Call) {
        incomingCallSnapshotTimer = fixedRateTimer("timer", false, 1000, 1000) {
423 424 425 426 427 428
            GlobalScope.launch(context = Dispatchers.Main) {
                if (call.state == Call.State.IncomingEarlyMedia) {
                    call.requestNotifyNextVideoFrameDecoded()
                }
            }
        }
Christophe Deschamps's avatar
Christophe Deschamps committed
429 430
    }

Christophe Deschamps's avatar
Christophe Deschamps committed
431

Christophe Deschamps's avatar
Christophe Deschamps committed
432 433 434 435
    fun displayMissedCallNotification(call: Call) {
        val missedCallCount: Int = call.core.missedCallsCount
        val body: String
        if (missedCallCount > 1) {
Christophe Deschamps's avatar
Christophe Deschamps committed
436
            body = Texts.get("notif_missed_calls", missedCallCount.toString())
Christophe Deschamps's avatar
Christophe Deschamps committed
437 438
            Log.i("[Notifications Manager] Updating missed calls notification count to $missedCallCount")
        } else {
Christophe Deschamps's avatar
Christophe Deschamps committed
439 440
            val name = DeviceStore.findDeviceByAddress(call.remoteAddress)?.name
                ?: call.remoteAddress.username
441
            body = Texts.get("notif_missed_call", name?:"device")
Christophe Deschamps's avatar
Christophe Deschamps committed
442 443 444 445 446
            Log.i("[Notifications Manager] Creating missed call notification")
        }

        val pendingIntent = NavDeepLinkBuilder(context)
            .setComponentName(MainActivity::class.java)
Christophe Deschamps's avatar
Christophe Deschamps committed
447 448
            .setGraph(R.navigation.fragments_graph)
            .setDestination(R.id.navigation_history)
Christophe Deschamps's avatar
Christophe Deschamps committed
449 450 451
            .createPendingIntent()

        val notification = NotificationCompat.Builder(
Christophe Deschamps's avatar
Christophe Deschamps committed
452 453
            context, Texts.get("notification_channel_incoming_call_id")
        )
Christophe Deschamps's avatar
Christophe Deschamps committed
454
            .setContentTitle(Texts.get("notif_missed_call_title"))
Christophe Deschamps's avatar
Christophe Deschamps committed
455
            .setContentText(body)
Christophe Deschamps's avatar
Christophe Deschamps committed
456
            .setSmallIcon(R.drawable.notification_missed)
Christophe Deschamps's avatar
Christophe Deschamps committed
457 458 459 460 461 462
            .setAutoCancel(true)
            .setContentIntent(pendingIntent)
            .setCategory(Notification.CATEGORY_EVENT)
            .setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
            .setWhen(System.currentTimeMillis())
            .setShowWhen(true)
Christophe Deschamps's avatar
Christophe Deschamps committed
463
            .setNumber(coreContext.core.missedCount())
Christophe Deschamps's avatar
Christophe Deschamps committed
464
            .setColor(Theme.getColor("color_s"))
Christophe Deschamps's avatar
Christophe Deschamps committed
465 466 467 468 469 470 471 472 473 474 475
            .build()
        notify(MISSED_CALLS_NOTIF_ID, notification)
    }

    fun dismissMissedCallNotification() {
        cancel(MISSED_CALLS_NOTIF_ID)
    }

    fun displayCallNotification(call: Call, useAsForeground: Boolean = false) {
        val notifiable = getNotifiableForCall(call)

Christophe Deschamps's avatar
Christophe Deschamps committed
476
        val stringResourceId: String
Christophe Deschamps's avatar
Christophe Deschamps committed
477 478 479 480
        val iconResourceId: Int
        val callActivity: Class<*>
        when (call.state) {
            Call.State.OutgoingRinging, Call.State.OutgoingProgress, Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia -> {
Christophe Deschamps's avatar
Christophe Deschamps committed
481 482 483
                callActivity = CallOutgoingActivity::class.java
                stringResourceId = Texts.get("notif_outgoing_call")
                iconResourceId = R.drawable.notification_phone
Christophe Deschamps's avatar
Christophe Deschamps committed
484 485
            }
            else -> {
Christophe Deschamps's avatar
Christophe Deschamps committed
486 487 488
                callActivity = CallInProgressActivity::class.java
                stringResourceId = Texts.get("notif_in_call")
                iconResourceId = R.drawable.notification_phone
Christophe Deschamps's avatar
Christophe Deschamps committed
489 490 491 492 493
            }
        }

        val callNotificationIntent = Intent(context, callActivity)
        callNotificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
Christophe Deschamps's avatar
Christophe Deschamps committed
494 495 496 497 498 499
        val pendingIntent = PendingIntent.getActivity(
            context,
            0,
            callNotificationIntent,
            PendingIntent.FLAG_UPDATE_CURRENT
        )
Christophe Deschamps's avatar
Christophe Deschamps committed
500 501


Christophe Deschamps's avatar
Christophe Deschamps committed
502
        val notification = NotificationCompat.Builder(
Christophe Deschamps's avatar
Christophe Deschamps committed
503 504 505 506 507 508
            context, Texts.get("notification_channel_service_id")
        )
            .setContentTitle(
                DeviceStore.findDeviceByAddress(call.remoteAddress)?.name
                    ?: call.remoteAddress.username
            )
Christophe Deschamps's avatar
Christophe Deschamps committed
509
            .setContentText(stringResourceId)
Christophe Deschamps's avatar
Christophe Deschamps committed
510 511 512 513 514 515 516 517 518
            .setSmallIcon(iconResourceId)
            .setAutoCancel(false)
            .setContentIntent(pendingIntent)
            .setCategory(Notification.CATEGORY_CALL)
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .setWhen(System.currentTimeMillis())
            .setShowWhen(true)
            .setOngoing(true)
Christophe Deschamps's avatar
Christophe Deschamps committed
519
            .setColor(Theme.getColor("color_s"))
Christophe Deschamps's avatar
Christophe Deschamps committed
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
            .addAction(getCallDeclineAction(notifiable.notificationId))
            .build()
        notify(notifiable.notificationId, notification)

        if (useAsForeground) {
            startForeground(notifiable.notificationId, notification)
        }
    }

    private fun dismissCallNotification(call: Call) {
        val address = call.remoteAddress?.asStringUriOnly()
        val notifiable: Notifiable? = callNotificationsMap[address]
        if (notifiable != null) {
            cancel(notifiable.notificationId)
            callNotificationsMap.remove(address)
        }
    }


    /* Notifications actions */

    private fun getCallAnswerAction(callId: Int): NotificationCompat.Action {

        val answerIntent = Intent(context, NotificationBroadcastReceiver::class.java)
        answerIntent.action = INTENT_ANSWER_CALL_NOTIF_ACTION
        answerIntent.putExtra(INTENT_NOTIF_ID, callId)

        val answerPendingIntent = PendingIntent.getBroadcast(
            context, callId, answerIntent, PendingIntent.FLAG_UPDATE_CURRENT
        )

        return NotificationCompat.Action.Builder(
Christophe Deschamps's avatar
Christophe Deschamps committed
552 553
            R.drawable.notification_phone,
            Texts.get("call_button_accept"),
Christophe Deschamps's avatar
Christophe Deschamps committed
554 555 556 557 558 559 560 561 562 563 564 565 566 567
            answerPendingIntent
        ).build()
    }

    private fun getCallDeclineAction(callId: Int): NotificationCompat.Action {
        val hangupIntent = Intent(context, NotificationBroadcastReceiver::class.java)
        hangupIntent.action = INTENT_HANGUP_CALL_NOTIF_ACTION
        hangupIntent.putExtra(INTENT_NOTIF_ID, callId)

        val hangupPendingIntent = PendingIntent.getBroadcast(
            context, callId, hangupIntent, PendingIntent.FLAG_UPDATE_CURRENT
        )

        return NotificationCompat.Action.Builder(
Christophe Deschamps's avatar
Christophe Deschamps committed
568 569
            R.drawable.notification_decline,
            Texts.get("call_button_decline"),
Christophe Deschamps's avatar
Christophe Deschamps committed
570 571 572 573 574 575
            hangupPendingIntent
        ).build()
    }


}