2019-04-02 16:08:43 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2019-07-02 15:27:08 +00:00
|
|
|
package im.vector.riotx.gplay.push.fcm
|
2019-04-02 16:08:43 +00:00
|
|
|
|
|
|
|
import android.os.Handler
|
|
|
|
import android.os.Looper
|
|
|
|
import android.text.TextUtils
|
2019-06-19 08:46:59 +00:00
|
|
|
import androidx.lifecycle.Lifecycle
|
|
|
|
import androidx.lifecycle.ProcessLifecycleOwner
|
2019-04-02 16:08:43 +00:00
|
|
|
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
|
2019-07-02 15:27:08 +00:00
|
|
|
import im.vector.riotx.BuildConfig
|
|
|
|
import im.vector.riotx.R
|
|
|
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
|
|
|
import im.vector.riotx.core.extensions.vectorComponent
|
|
|
|
import im.vector.riotx.core.preference.BingRule
|
|
|
|
import im.vector.riotx.core.pushers.PushersManager
|
|
|
|
import im.vector.riotx.features.badge.BadgeProxy
|
|
|
|
import im.vector.riotx.features.notifications.NotifiableEventResolver
|
|
|
|
import im.vector.riotx.features.notifications.NotifiableMessageEvent
|
|
|
|
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
|
|
|
import im.vector.riotx.features.notifications.SimpleNotifiableEvent
|
2019-07-09 14:10:22 +00:00
|
|
|
import im.vector.riotx.features.settings.VectorPreferences
|
2019-07-02 15:27:08 +00:00
|
|
|
import im.vector.riotx.push.fcm.FcmHelper
|
2019-04-02 16:08:43 +00:00
|
|
|
import timber.log.Timber
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class extending FirebaseMessagingService.
|
|
|
|
*/
|
|
|
|
class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
|
|
|
|
2019-06-28 07:35:34 +00:00
|
|
|
private lateinit var notificationDrawerManager: NotificationDrawerManager
|
|
|
|
private lateinit var notifiableEventResolver: NotifiableEventResolver
|
|
|
|
private lateinit var pusherManager: PushersManager
|
|
|
|
private lateinit var activeSessionHolder: ActiveSessionHolder
|
2019-04-02 16:08:43 +00:00
|
|
|
|
|
|
|
// UI handler
|
|
|
|
private val mUIHandler by lazy {
|
|
|
|
Handler(Looper.getMainLooper())
|
|
|
|
}
|
|
|
|
|
2019-06-27 16:56:23 +00:00
|
|
|
override fun onCreate() {
|
|
|
|
super.onCreate()
|
2019-06-28 07:35:34 +00:00
|
|
|
notificationDrawerManager = vectorComponent().notificationDrawerManager()
|
|
|
|
notifiableEventResolver = vectorComponent().notifiableEventResolver()
|
|
|
|
pusherManager = vectorComponent().pusherManager()
|
|
|
|
activeSessionHolder = vectorComponent().activeSessionHolder()
|
2019-06-27 16:56:23 +00:00
|
|
|
}
|
|
|
|
|
2019-04-02 16:08:43 +00:00
|
|
|
/**
|
|
|
|
* Called when message is received.
|
|
|
|
*
|
|
|
|
* @param message the message
|
|
|
|
*/
|
|
|
|
override fun onMessageReceived(message: RemoteMessage?) {
|
2019-07-09 14:10:22 +00:00
|
|
|
if (!VectorPreferences.areNotificationEnabledForDevice(applicationContext)) {
|
2019-06-24 08:04:03 +00:00
|
|
|
Timber.i("Notification are disabled for this device")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-04-02 16:08:43 +00:00
|
|
|
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) {
|
2019-06-22 15:08:45 +00:00
|
|
|
Timber.i("## onMessageReceived() %s", message.data.toString())
|
|
|
|
Timber.i("## onMessageReceived() from FCM with priority %s", message.priority)
|
2019-04-02 16:08:43 +00:00
|
|
|
}
|
2019-06-19 08:46:59 +00:00
|
|
|
mUIHandler.post {
|
|
|
|
if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
|
|
|
//we are in foreground, let the sync do the things?
|
|
|
|
Timber.v("PUSH received in a foreground state, ignore")
|
|
|
|
} else {
|
|
|
|
onMessageReceivedInternal(message.data)
|
|
|
|
}
|
2019-04-02 16:08:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)
|
2019-06-19 08:46:59 +00:00
|
|
|
if (refreshedToken == null) {
|
|
|
|
Timber.w("onNewToken:received null token")
|
|
|
|
} else {
|
2019-07-09 14:10:22 +00:00
|
|
|
if (VectorPreferences.areNotificationEnabledForDevice(applicationContext) && activeSessionHolder.hasActiveSession()) {
|
2019-06-24 08:04:03 +00:00
|
|
|
pusherManager.registerPusherWithFcmKey(refreshedToken)
|
|
|
|
}
|
2019-06-19 08:46:59 +00:00
|
|
|
}
|
2019-04-02 16:08:43 +00:00
|
|
|
}
|
|
|
|
|
2019-06-19 08:46:59 +00:00
|
|
|
/**
|
|
|
|
* Called when the FCM server deletes pending messages. This may be due to:
|
|
|
|
* - Too many messages stored on the FCM server.
|
|
|
|
* This can occur when an app's servers send a bunch of non-collapsible messages to FCM servers while the device is offline.
|
|
|
|
* - The device hasn't connected in a long time and the app server has recently (within the last 4 weeks)
|
|
|
|
* sent a message to the app on that device.
|
|
|
|
*
|
|
|
|
* It is recommended that the app do a full sync with the app server after receiving this call.
|
|
|
|
*/
|
2019-04-02 16:08:43 +00:00
|
|
|
override fun onDeletedMessages() {
|
2019-05-20 15:13:08 +00:00
|
|
|
Timber.v("## onDeletedMessages()")
|
2019-04-02 16:08:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal receive method
|
|
|
|
*
|
|
|
|
* @param data Data map containing message data as key/value pairs.
|
|
|
|
* For Set of keys use data.keySet().
|
|
|
|
*/
|
2019-06-19 08:46:59 +00:00
|
|
|
private fun onMessageReceivedInternal(data: Map<String, String>) {
|
2019-04-02 16:08:43 +00:00
|
|
|
try {
|
|
|
|
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
|
|
|
|
Timber.i("## onMessageReceivedInternal() : $data")
|
|
|
|
}
|
2019-06-19 08:46:59 +00:00
|
|
|
val eventId = data["event_id"]
|
|
|
|
val roomId = data["room_id"]
|
|
|
|
if (eventId == null || roomId == null) {
|
|
|
|
Timber.e("## onMessageReceivedInternal() missing eventId and/or roomId")
|
|
|
|
return
|
|
|
|
}
|
2019-04-02 16:08:43 +00:00
|
|
|
// update the badge counter
|
|
|
|
val unreadCount = data.get("unread")?.let { Integer.parseInt(it) } ?: 0
|
|
|
|
BadgeProxy.updateBadgeCount(applicationContext, unreadCount)
|
|
|
|
|
2019-06-27 16:56:23 +00:00
|
|
|
val session = activeSessionHolder.getSafeActiveSession()
|
2019-04-02 16:08:43 +00:00
|
|
|
|
2019-06-19 08:46:59 +00:00
|
|
|
if (session == null) {
|
|
|
|
Timber.w("## Can't sync from push, no current session")
|
2019-04-02 16:08:43 +00:00
|
|
|
} else {
|
2019-06-19 08:46:59 +00:00
|
|
|
if (isEventAlreadyKnown(eventId, roomId)) {
|
2019-08-07 14:10:50 +00:00
|
|
|
Timber.i("Ignoring push, event already known")
|
2019-06-19 08:46:59 +00:00
|
|
|
} else {
|
|
|
|
Timber.v("Requesting background sync")
|
2019-06-21 16:32:17 +00:00
|
|
|
session.requireBackgroundSync()
|
2019-06-19 08:46:59 +00:00
|
|
|
}
|
2019-04-02 16:08:43 +00:00
|
|
|
}
|
2019-06-19 08:46:59 +00:00
|
|
|
|
2019-04-02 16:08:43 +00:00
|
|
|
} 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 {
|
2019-06-27 16:56:23 +00:00
|
|
|
val session = activeSessionHolder.getSafeActiveSession() ?: return false
|
2019-06-19 08:46:59 +00:00
|
|
|
val room = session.getRoom(roomId) ?: return false
|
|
|
|
return room.getTimeLineEvent(eventId) != null
|
2019-04-02 16:08:43 +00:00
|
|
|
} catch (e: Exception) {
|
2019-06-22 15:08:45 +00:00
|
|
|
Timber.e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined")
|
2019-04-02 16:08:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
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(
|
2019-07-16 13:50:07 +00:00
|
|
|
session.myUserId,
|
2019-04-02 16:08:43 +00:00
|
|
|
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)
|
2019-06-20 13:22:40 +00:00
|
|
|
notificationDrawerManager.refreshNotificationDrawer()
|
2019-04-02 16:08:43 +00:00
|
|
|
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
|
2019-06-22 15:08:45 +00:00
|
|
|
val event = parseEvent(data) ?: return
|
2019-04-02 16:08:43 +00:00
|
|
|
|
2019-06-22 15:08:45 +00:00
|
|
|
val notifiableEvent = notifiableEventResolver.resolveEvent(event, session)
|
2019-04-02 16:08:43 +00:00
|
|
|
|
2019-06-22 15:08:45 +00:00
|
|
|
if (notifiableEvent == null) {
|
|
|
|
Timber.e("Unsupported notifiable event ${eventId}")
|
|
|
|
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
|
|
|
|
Timber.e("--> ${event}")
|
|
|
|
}
|
|
|
|
} else {
|
2019-04-02 16:08:43 +00:00
|
|
|
|
|
|
|
|
2019-06-22 15:08:45 +00:00
|
|
|
if (notifiableEvent is NotifiableMessageEvent) {
|
|
|
|
if (TextUtils.isEmpty(notifiableEvent.senderName)) {
|
|
|
|
notifiableEvent.senderName = data["sender_display_name"]
|
2019-06-27 16:56:23 +00:00
|
|
|
?: data["sender"] ?: ""
|
2019-06-22 15:08:45 +00:00
|
|
|
}
|
|
|
|
if (TextUtils.isEmpty(notifiableEvent.roomName)) {
|
|
|
|
notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: ""
|
2019-04-02 16:08:43 +00:00
|
|
|
}
|
|
|
|
}
|
2019-06-22 15:08:45 +00:00
|
|
|
|
|
|
|
notifiableEvent.isPushGatewayEvent = true
|
2019-07-16 13:50:07 +00:00
|
|
|
notifiableEvent.matrixID = session.myUserId
|
2019-06-22 15:08:45 +00:00
|
|
|
notificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
|
|
|
|
notificationDrawerManager.refreshNotificationDrawer()
|
2019-04-02 16:08:43 +00:00
|
|
|
}
|
|
|
|
}
|
2019-06-22 15:08:45 +00:00
|
|
|
|
2019-04-02 16:08:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun findRoomNameBestEffort(data: Map<String, String>, session: Session?): String? {
|
2019-06-22 15:08:45 +00:00
|
|
|
val roomName: String? = data["room_name"]
|
2019-04-02 16:08:43 +00:00
|
|
|
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"],
|
2019-06-27 16:56:23 +00:00
|
|
|
senderId = data["sender"],
|
|
|
|
roomId = data["room_id"],
|
|
|
|
type = data.getValue("type"),
|
2019-04-02 16:08:43 +00:00
|
|
|
// TODO content = data.getValue("content"),
|
2019-06-27 16:56:23 +00:00
|
|
|
originServerTs = System.currentTimeMillis())
|
2019-04-02 16:08:43 +00:00
|
|
|
} catch (e: Exception) {
|
2019-06-22 15:08:45 +00:00
|
|
|
Timber.e(e, "buildEvent fails ")
|
2019-04-02 16:08:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|