mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-01-31 13:48:20 +01:00
[Firebase] Implement new custom FCM service.
This commit is contained in:
parent
92ba7248ef
commit
8e2297359c
@ -172,13 +172,13 @@
|
|||||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver android:name=".sync.FirebaseBroadcastReceiver"
|
<!--<receiver android:name=".sync.FirebaseBroadcastReceiver"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:permission="com.google.android.c2dm.permission.SEND">
|
android:permission="com.google.android.c2dm.permission.SEND">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>-->
|
||||||
<receiver android:name=".receivers.SzkolnyReceiver"
|
<receiver android:name=".receivers.SzkolnyReceiver"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -193,16 +193,23 @@
|
|||||||
____) | __/ | \ V /| | (_| __/\__ \
|
____) | __/ | \ V /| | (_| __/\__ \
|
||||||
|_____/ \___|_| \_/ |_|\___\___||___/
|
|_____/ \___|_| \_/ |_|\___\___||___/
|
||||||
-->
|
-->
|
||||||
<service android:name=".sync.MyFirebaseMessagingService"
|
<!--<service android:name=".sync.MyFirebaseMessagingService"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
|
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>-->
|
||||||
<service android:name=".receivers.BootReceiver$NotificationActionService" />
|
<service android:name=".receivers.BootReceiver$NotificationActionService" />
|
||||||
<service android:name=".Notifier$GetDataRetryService" />
|
<service android:name=".Notifier$GetDataRetryService" />
|
||||||
<service android:name=".data.api.ApiService" />
|
<service android:name=".data.api.ApiService" />
|
||||||
|
<service android:name=".sync.MyFirebaseService"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter android:priority="10000000">
|
||||||
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
|
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
_____ _ _
|
_____ _ _
|
||||||
|
@ -3,6 +3,7 @@ package pl.szczodrzynski.edziennik
|
|||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
@ -643,6 +644,28 @@ fun Bundle(vararg properties: Pair<String, Any?>): Bundle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Bundle.toJsonObject(): JsonObject {
|
||||||
|
val json = JsonObject()
|
||||||
|
keySet()?.forEach { key ->
|
||||||
|
get(key)?.let {
|
||||||
|
when (it) {
|
||||||
|
is String -> json.addProperty(key, it)
|
||||||
|
is Char -> json.addProperty(key, it)
|
||||||
|
is Int -> json.addProperty(key, it)
|
||||||
|
is Long -> json.addProperty(key, it)
|
||||||
|
is Float -> json.addProperty(key, it)
|
||||||
|
is Short -> json.addProperty(key, it)
|
||||||
|
is Double -> json.addProperty(key, it)
|
||||||
|
is Boolean -> json.addProperty(key, it)
|
||||||
|
is Bundle -> json.add(key, it.toJsonObject())
|
||||||
|
else -> json.addProperty(key, it.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
fun Intent.toJsonObject() = extras?.toJsonObject()
|
||||||
|
|
||||||
fun JsonArray?.isNullOrEmpty(): Boolean = (this?.size() ?: 0) == 0
|
fun JsonArray?.isNullOrEmpty(): Boolean = (this?.size() ?: 0) == 0
|
||||||
fun JsonArray.isEmpty(): Boolean = this.size() == 0
|
fun JsonArray.isEmpty(): Boolean = this.size() == 0
|
||||||
operator fun JsonArray.plusAssign(o: JsonElement) = this.add(o)
|
operator fun JsonArray.plusAssign(o: JsonElement) = this.add(o)
|
||||||
|
@ -8,22 +8,18 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.legacy.content.WakefulBroadcastReceiver
|
import androidx.legacy.content.WakefulBroadcastReceiver
|
||||||
import com.google.firebase.messaging.RemoteMessage
|
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import pl.szczodrzynski.edziennik.App
|
|
||||||
|
|
||||||
class FirebaseBroadcastReceiver : WakefulBroadcastReceiver() {
|
class FirebaseBroadcastReceiver : WakefulBroadcastReceiver() {
|
||||||
|
companion object {
|
||||||
val TAG: String = FirebaseBroadcastReceiver::class.java.simpleName
|
private const val TAG = "FirebaseBroadcast"
|
||||||
|
}
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val extras = intent.extras
|
||||||
val json = JsonObject()
|
val json = JsonObject()
|
||||||
|
extras?.keySet()?.forEach { key ->
|
||||||
val dataBundle = intent.extras
|
extras.get(key)?.let {
|
||||||
if (dataBundle != null)
|
|
||||||
for (key in dataBundle.keySet()) {
|
|
||||||
dataBundle.get(key)?.let {
|
|
||||||
when (it) {
|
when (it) {
|
||||||
is String -> json.addProperty(key, it)
|
is String -> json.addProperty(key, it)
|
||||||
is Int -> json.addProperty(key, it)
|
is Int -> json.addProperty(key, it)
|
||||||
@ -34,13 +30,6 @@ class FirebaseBroadcastReceiver : WakefulBroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Log.d(TAG, "Intent(action=${intent?.action}, extras=$json)")
|
||||||
Log.d(TAG, "Firebase got push from Librus Broadcast ${json}")
|
|
||||||
|
|
||||||
val sharedPreferences = context.getSharedPreferences("pushtest_broadcast", Context.MODE_PRIVATE)
|
|
||||||
sharedPreferences.edit().putString(
|
|
||||||
System.currentTimeMillis().toString(),
|
|
||||||
json.toString()
|
|
||||||
).apply()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-1-11.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.sync
|
||||||
|
|
||||||
|
class FirebaseSendException(reason: String?) : Exception(reason) {
|
||||||
|
companion object {
|
||||||
|
const val ERROR_UNKNOWN = 0
|
||||||
|
const val ERROR_INVALID_PARAMETERS = 1
|
||||||
|
const val ERROR_SIZE = 2
|
||||||
|
const val ERROR_TTL_EXCEEDED = 3
|
||||||
|
const val ERROR_TOO_MANY_MESSAGES = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
val errorCode = when (reason) {
|
||||||
|
"service_not_available" -> ERROR_TTL_EXCEEDED
|
||||||
|
"toomanymessages" -> ERROR_TOO_MANY_MESSAGES
|
||||||
|
"invalid_parameters" -> ERROR_INVALID_PARAMETERS
|
||||||
|
"messagetoobig" -> ERROR_SIZE
|
||||||
|
"missing_to" -> ERROR_INVALID_PARAMETERS
|
||||||
|
else -> ERROR_UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-1-11.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.sync
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.PendingIntent.CanceledException
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.firebase.iid.zzaq
|
||||||
|
import com.google.firebase.iid.zzv
|
||||||
|
import com.google.firebase.messaging.MessagingAnalytics
|
||||||
|
import com.google.firebase.messaging.zzc
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import pl.szczodrzynski.edziennik.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@SuppressLint("Registered")
|
||||||
|
open class FirebaseService : zzc() {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "FirebaseService"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val messageQueue = ArrayDeque<String>(10)
|
||||||
|
|
||||||
|
open fun onMessageReceived(message: Message) = Unit
|
||||||
|
open fun onDeletedMessages() = Unit
|
||||||
|
open fun onMessageSent(messageId: String?) = Unit
|
||||||
|
open fun onSendError(messageId: String?, exception: Exception) = Unit
|
||||||
|
open fun onNewToken(token: String?) = Unit
|
||||||
|
|
||||||
|
// apparently this gets the correct intent from some
|
||||||
|
// kind of queue inside Firebase's InstanceID Receiver
|
||||||
|
final override fun zza(intent: Intent?) = zzaq.zza()?.zzb()
|
||||||
|
final override fun zzb(intent: Intent?): Boolean {
|
||||||
|
val action = intent?.action
|
||||||
|
if (action == "com.google.firebase.messaging.NOTIFICATION_OPEN") {
|
||||||
|
intent.getParcelableExtra<PendingIntent>("pending_intent")?.let {
|
||||||
|
try {
|
||||||
|
it.send()
|
||||||
|
} catch (e: CanceledException) {
|
||||||
|
Log.e(TAG, "Notification pending intent canceled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MessagingAnalytics.shouldUploadMetrics(intent)) {
|
||||||
|
MessagingAnalytics.logNotificationOpen(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
final override fun zzc(intent: Intent?) {
|
||||||
|
val action = intent?.action
|
||||||
|
val json = intent?.toJsonObject()
|
||||||
|
Log.d(TAG, "zzc Intent(action=$action, extras=$json)")
|
||||||
|
if (action == null || json == null)
|
||||||
|
return
|
||||||
|
|
||||||
|
when (action) {
|
||||||
|
"com.google.firebase.messaging.NOTIFICATION_DISMISS" -> {
|
||||||
|
if (MessagingAnalytics.shouldUploadMetrics(intent)) {
|
||||||
|
MessagingAnalytics.logNotificationDismiss(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"com.google.firebase.messaging.NEW_TOKEN" -> {
|
||||||
|
onNewToken(json.getString("token"))
|
||||||
|
}
|
||||||
|
"com.google.android.c2dm.intent.RECEIVE",
|
||||||
|
"com.google.firebase.messaging.RECEIVE_DIRECT_BOOT" -> {
|
||||||
|
val messageId = json.getString("google.message_id")
|
||||||
|
if (messageId != null) {
|
||||||
|
// send back an acknowledgement to Google Play Services
|
||||||
|
val ackBundle = Bundle(
|
||||||
|
"google.message_id" to messageId
|
||||||
|
)
|
||||||
|
zzv.zza(this).zza(2, ackBundle)
|
||||||
|
}
|
||||||
|
// check for duplicate message
|
||||||
|
// and add it to queue
|
||||||
|
if (messageId.isNotNullNorEmpty()) {
|
||||||
|
if (messageQueue.contains(messageId)) {
|
||||||
|
Log.d(TAG, "Received duplicate message: $messageId")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (messageQueue.size >= 10)
|
||||||
|
messageQueue.remove()
|
||||||
|
messageQueue += messageId
|
||||||
|
}
|
||||||
|
// process the received message
|
||||||
|
processMessage(messageId, json, intent)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.d(TAG, "Unknown intent action: $action")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processMessage(messageId: String?, json: JsonObject, intent: Intent) {
|
||||||
|
// remove something that the original FMS removes
|
||||||
|
json.remove("androidx.contentpager.content.wakelockid")
|
||||||
|
|
||||||
|
// get the message type
|
||||||
|
when (val it = json.getString("message_type") ?: "gcm") {
|
||||||
|
"gcm" -> { // 0
|
||||||
|
if (MessagingAnalytics.shouldUploadMetrics(intent)) {
|
||||||
|
MessagingAnalytics.logNotificationReceived(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessageReceived(Message(messageId, json))
|
||||||
|
}
|
||||||
|
"deleted_messages" -> { // 1
|
||||||
|
onDeletedMessages()
|
||||||
|
}
|
||||||
|
"send_event" -> { // 2
|
||||||
|
onMessageSent(messageId)
|
||||||
|
}
|
||||||
|
"send_error" -> { // 3
|
||||||
|
onSendError(
|
||||||
|
messageId ?: json.getString("message_id"),
|
||||||
|
FirebaseSendException(json.getString("error"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.w(TAG, "Received message with unknown type: $it")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Message(val messageId: String?, private val json: JsonObject) {
|
||||||
|
val data = json.deepCopy()
|
||||||
|
val from by lazy { s("from") ?: "" }
|
||||||
|
val to by lazy { s("google.to") }
|
||||||
|
val messageType by lazy { s("message_type") }
|
||||||
|
val collapseKey by lazy { s("collapse_key") }
|
||||||
|
val sentTime by lazy { l("google.sent_time") }
|
||||||
|
val ttl by lazy { i("google.ttl") }
|
||||||
|
val originalPriority by lazy { getPriority(s("google.original_priority") ?: s("priority")) }
|
||||||
|
val priority by lazy { getPriority(
|
||||||
|
s("google.delivered_priority") ?: if (i("google.priority_reduced") == 1)
|
||||||
|
"normal"
|
||||||
|
else s("google.priority")
|
||||||
|
) }
|
||||||
|
val isNotificationMessage by lazy { isNotificationMessage(json) }
|
||||||
|
val notificationTitle by lazy { s("gcm.notification.title") }
|
||||||
|
val notificationText by lazy { s("gcm.notification.body") }
|
||||||
|
|
||||||
|
init {
|
||||||
|
data.also {
|
||||||
|
val toRemove = mutableListOf<String>()
|
||||||
|
it.keySet().forEach { key ->
|
||||||
|
if (key.startsWith("google.")
|
||||||
|
|| key.startsWith("gcm.")
|
||||||
|
|| key == "from"
|
||||||
|
|| key == "message_type"
|
||||||
|
|| key == "collapse_key")
|
||||||
|
toRemove += key
|
||||||
|
}
|
||||||
|
toRemove.forEach { key ->
|
||||||
|
it.remove(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun s(key: String): String? = json.getString(key)
|
||||||
|
private fun l(key: String): Long = json.getLong(key) ?: 0L
|
||||||
|
private fun i(key: String): Int = json.getInt(key) ?: 0
|
||||||
|
private fun isNotificationMessage(json: JsonObject): Boolean {
|
||||||
|
return json.getInt("gcm.n.e") == 1
|
||||||
|
|| json.getInt("gcm.notification.e") == 1
|
||||||
|
|| json.getString("gcm.n.icon") != null
|
||||||
|
|| json.getString("gcm.notification.icon") != null
|
||||||
|
}
|
||||||
|
private fun getPriority(str: String?): Int {
|
||||||
|
return when (str) {
|
||||||
|
"high" -> 1
|
||||||
|
"normal" -> 2
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Message(messageId=$messageId, from=$from, data=$data)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-1-11.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.sync
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
|
||||||
|
class MyFirebaseService : FirebaseService() {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "MyFirebaseService"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val app by lazy { applicationContext as App }
|
||||||
|
|
||||||
|
override fun onNewToken(token: String?) {
|
||||||
|
Log.d(TAG, "Got new token: $token")
|
||||||
|
app.config.sync.tokenApp = token
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMessageReceived(message: Message) {
|
||||||
|
Log.d(TAG, "Message received from ${message.from}: $message")
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user