BayernMessenger/vector/src/gplay/java/im/vector/riotredesign/push/fcm/VectorFirebaseMessagingServ...

272 lines
10 KiB
Kotlin
Executable File

/*
* Copyright 2019 New Vector Ltd
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.push.fcm
import android.os.Handler
import android.os.Looper
import android.text.TextUtils
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.riotredesign.BuildConfig
import im.vector.riotredesign.R
import im.vector.riotredesign.core.preference.BingRule
import im.vector.riotredesign.features.badge.BadgeProxy
import im.vector.riotredesign.features.notifications.NotifiableEventResolver
import im.vector.riotredesign.features.notifications.NotifiableMessageEvent
import im.vector.riotredesign.features.notifications.NotificationDrawerManager
import im.vector.riotredesign.features.notifications.SimpleNotifiableEvent
import org.koin.android.ext.android.inject
import timber.log.Timber
/**
* Class extending FirebaseMessagingService.
*/
class VectorFirebaseMessagingService : FirebaseMessagingService() {
val notificationDrawerManager by inject<NotificationDrawerManager>()
private val notifiableEventResolver by lazy {
NotifiableEventResolver(this)
}
// UI handler
private val mUIHandler by lazy {
Handler(Looper.getMainLooper())
}
/**
* Called when message is received.
*
* @param message the message
*/
override fun onMessageReceived(message: RemoteMessage?) {
if (message == null || message.data == null) {
Timber.e("## onMessageReceived() : received a null message or message with no data")
return
}
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.i("## onMessageReceived()" + message.data.toString())
Timber.i("## onMessageReceived() from FCM with priority " + message.priority)
}
//safe guard
/* TODO
val pushManager = Matrix.getInstance(applicationContext).pushManager
if (!pushManager.areDeviceNotificationsAllowed()) {
Timber.i("## onMessageReceived() : the notifications are disabled")
return
}
*/
//TODO if the app is in foreground, we could just ignore this. The sync loop is already going?
// TODO mUIHandler.post { onMessageReceivedInternal(message.data, pushManager) }
}
/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. Note that this is also called
* when the InstanceID token is initially generated, so this is where
* you retrieve the token.
*/
override fun onNewToken(refreshedToken: String?) {
Timber.i("onNewToken: FCM Token has been updated")
FcmHelper.storeFcmToken(this, refreshedToken)
// TODO Matrix.getInstance(this)?.pushManager?.resetFCMRegistration(refreshedToken)
}
override fun onDeletedMessages() {
Timber.d("## onDeletedMessages()")
}
/**
* Internal receive method
*
* @param data Data map containing message data as key/value pairs.
* For Set of keys use data.keySet().
*/
private fun onMessageReceivedInternal(data: Map<String, String> /*, pushManager: PushManager*/) {
try {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.i("## onMessageReceivedInternal() : $data")
}
// update the badge counter
val unreadCount = data.get("unread")?.let { Integer.parseInt(it) } ?: 0
BadgeProxy.updateBadgeCount(applicationContext, unreadCount)
/* TODO
val session = Matrix.getInstance(applicationContext)?.defaultSession
if (VectorApp.isAppInBackground() && !pushManager.isBackgroundSyncAllowed) {
//Notification contains metadata and maybe data information
handleNotificationWithoutSyncingMode(data, session)
} else {
// Safe guard... (race?)
if (isEventAlreadyKnown(data["event_id"], data["room_id"])) return
//Catch up!!
EventStreamServiceX.onPushReceived(this)
}
*/
} catch (e: Exception) {
Timber.e(e, "## onMessageReceivedInternal() failed : " + e.message)
}
}
// check if the event was not yet received
// a previous catchup might have already retrieved the notified event
private fun isEventAlreadyKnown(eventId: String?, roomId: String?): Boolean {
if (null != eventId && null != roomId) {
try {
/* TODO
val sessions = Matrix.getInstance(applicationContext).sessions
if (null != sessions && !sessions.isEmpty()) {
for (session in sessions) {
if (session.dataHandler?.store?.isReady == true) {
session.dataHandler.store?.getEvent(eventId, roomId)?.let {
Timber.e("## isEventAlreadyKnown() : ignore the event " + eventId
+ " in room " + roomId + " because it is already known")
return true
}
}
}
}
*/
} catch (e: Exception) {
Timber.e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined " + e.message)
}
}
return false
}
private fun handleNotificationWithoutSyncingMode(data: Map<String, String>, session: Session?) {
if (session == null) {
Timber.e("## handleNotificationWithoutSyncingMode cannot find session")
return
}
// The Matrix event ID of the event being notified about.
// This is required if the notification is about a particular Matrix event.
// It may be omitted for notifications that only contain updated badge counts.
// This ID can and should be used to detect duplicate notification requests.
val eventId = data["event_id"] ?: return //Just ignore
val eventType = data["type"]
if (eventType == null) {
//Just add a generic unknown event
val simpleNotifiableEvent = SimpleNotifiableEvent(
session.sessionParams.credentials.userId,
eventId,
true, //It's an issue in this case, all event will bing even if expected to be silent.
title = getString(R.string.notification_unknown_new_event),
description = "",
type = null,
timestamp = System.currentTimeMillis(),
soundName = BingRule.ACTION_VALUE_DEFAULT,
isPushGatewayEvent = true
)
notificationDrawerManager.onNotifiableEventReceived(simpleNotifiableEvent)
notificationDrawerManager.refreshNotificationDrawer(null)
return
} else {
val event = parseEvent(data)
if (event?.roomId == null) {
//unsupported event
Timber.e("Received an event with no room id")
return
} else {
var notifiableEvent = notifiableEventResolver.resolveEvent(event, null, null /* TODO session.fulfillRule(event) */, session)
if (notifiableEvent == null) {
Timber.e("Unsupported notifiable event ${eventId}")
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.e("--> ${event}")
}
} else {
if (notifiableEvent is NotifiableMessageEvent) {
if (TextUtils.isEmpty(notifiableEvent.senderName)) {
notifiableEvent.senderName = data["sender_display_name"] ?: data["sender"] ?: ""
}
if (TextUtils.isEmpty(notifiableEvent.roomName)) {
notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: ""
}
}
notifiableEvent.isPushGatewayEvent = true
notifiableEvent.matrixID = session.sessionParams.credentials.userId
notificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
notificationDrawerManager.refreshNotificationDrawer(null)
}
}
}
}
private fun findRoomNameBestEffort(data: Map<String, String>, session: Session?): String? {
var roomName: String? = data["room_name"]
val roomId = data["room_id"]
if (null == roomName && null != roomId) {
// Try to get the room name from our store
/*
TODO
if (session?.dataHandler?.store?.isReady == true) {
val room = session.getRoom(roomId)
roomName = room?.getRoomDisplayName(this)
}
*/
}
return roomName
}
/**
* Try to create an event from the FCM data
*
* @param data the FCM data
* @return the event
*/
private fun parseEvent(data: Map<String, String>?): Event? {
// accept only event with room id.
if (null == data || !data.containsKey("room_id") || !data.containsKey("event_id")) {
return null
}
try {
return Event(eventId = data["event_id"],
sender = data["sender"],
roomId = data["room_id"],
type = data.getValue("type"),
// TODO content = data.getValue("content"),
originServerTs = System.currentTimeMillis())
} catch (e: Exception) {
Timber.e(e, "buildEvent fails " + e.localizedMessage)
}
return null
}
}