Changeset View
Standalone View
native/android/app/src/main/java/app/comm/android/notifications/CommNotificationsHandler.java
package app.comm.android.notifications; | package app.comm.android.notifications; | ||||
import android.app.Notification; | import android.app.Notification; | ||||
import android.app.NotificationManager; | import android.app.NotificationManager; | ||||
import android.app.PendingIntent; | import android.app.PendingIntent; | ||||
import android.content.Context; | import android.content.Context; | ||||
import android.content.Intent; | import android.content.Intent; | ||||
import android.graphics.Bitmap; | import android.graphics.Bitmap; | ||||
import android.graphics.BitmapFactory; | import android.graphics.BitmapFactory; | ||||
import android.os.Bundle; | import android.os.Bundle; | ||||
import android.service.notification.StatusBarNotification; | import android.service.notification.StatusBarNotification; | ||||
import android.util.JsonReader; | |||||
import android.util.Log; | import android.util.Log; | ||||
import androidx.core.app.NotificationCompat; | import androidx.core.app.NotificationCompat; | ||||
import androidx.lifecycle.Lifecycle; | import androidx.lifecycle.Lifecycle; | ||||
import androidx.lifecycle.ProcessLifecycleOwner; | import androidx.lifecycle.ProcessLifecycleOwner; | ||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager; | import androidx.localbroadcastmanager.content.LocalBroadcastManager; | ||||
import app.comm.android.ExpoUtils; | import app.comm.android.ExpoUtils; | ||||
import app.comm.android.MainActivity; | import app.comm.android.MainActivity; | ||||
import app.comm.android.R; | import app.comm.android.R; | ||||
import app.comm.android.fbjni.CommSecureStore; | import app.comm.android.fbjni.CommSecureStore; | ||||
import app.comm.android.fbjni.GlobalDBSingleton; | import app.comm.android.fbjni.GlobalDBSingleton; | ||||
import app.comm.android.fbjni.MessageOperationsUtilities; | import app.comm.android.fbjni.MessageOperationsUtilities; | ||||
import app.comm.android.fbjni.NetworkModule; | import app.comm.android.fbjni.NetworkModule; | ||||
import app.comm.android.fbjni.NotificationsCryptoModule; | |||||
import app.comm.android.fbjni.ThreadOperations; | import app.comm.android.fbjni.ThreadOperations; | ||||
import com.google.firebase.messaging.FirebaseMessagingService; | import com.google.firebase.messaging.FirebaseMessagingService; | ||||
import com.google.firebase.messaging.RemoteMessage; | import com.google.firebase.messaging.RemoteMessage; | ||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | |||||
import java.io.StringReader; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import java.util.Set; | |||||
import me.leolin.shortcutbadger.ShortcutBadger; | import me.leolin.shortcutbadger.ShortcutBadger; | ||||
public class CommNotificationsHandler extends FirebaseMessagingService { | public class CommNotificationsHandler extends FirebaseMessagingService { | ||||
private static final String BADGE_KEY = "badge"; | private static final String BADGE_KEY = "badge"; | ||||
private static final String BADGE_ONLY_KEY = "badgeOnly"; | private static final String BADGE_ONLY_KEY = "badgeOnly"; | ||||
private static final String BACKGROUND_NOTIF_TYPE_KEY = "backgroundNotifType"; | private static final String BACKGROUND_NOTIF_TYPE_KEY = "backgroundNotifType"; | ||||
private static final String SET_UNREAD_STATUS_KEY = "setUnreadStatus"; | private static final String SET_UNREAD_STATUS_KEY = "setUnreadStatus"; | ||||
private static final String NOTIF_ID_KEY = "id"; | private static final String NOTIF_ID_KEY = "id"; | ||||
private static final String ENCRYPTED_PAYLOAD_KEY = "encryptedPayload"; | |||||
private static final String ENCRYPTION_FAILED_KEY = "encryptionFailed"; | |||||
private static final String CHANNEL_ID = "default"; | private static final String CHANNEL_ID = "default"; | ||||
private static final long[] VIBRATION_SPEC = {500, 500}; | private static final long[] VIBRATION_SPEC = {500, 500}; | ||||
private Bitmap displayableNotificationLargeIcon; | private Bitmap displayableNotificationLargeIcon; | ||||
private NotificationManager notificationManager; | private NotificationManager notificationManager; | ||||
private LocalBroadcastManager localBroadcastManager; | private LocalBroadcastManager localBroadcastManager; | ||||
public static final String RESCIND_KEY = "rescind"; | public static final String RESCIND_KEY = "rescind"; | ||||
Show All 29 Lines | public class CommNotificationsHandler extends FirebaseMessagingService { | ||||
@Override | @Override | ||||
public void onMessageReceived(RemoteMessage message) { | public void onMessageReceived(RemoteMessage message) { | ||||
String rescind = message.getData().get(RESCIND_KEY); | String rescind = message.getData().get(RESCIND_KEY); | ||||
if ("true".equals(rescind) && | if ("true".equals(rescind) && | ||||
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { | android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { | ||||
handleNotificationRescind(message); | handleNotificationRescind(message); | ||||
} | } | ||||
if (message.getData().get(ENCRYPTED_PAYLOAD_KEY) != null) { | |||||
try { | |||||
message = this.decryptRemoteMessage(message); | |||||
} catch (IOException e) { | |||||
Log.w( | |||||
"COMM", "IO exception while parsing notification JSON payload.", e); | |||||
return; | |||||
} catch (IllegalStateException e) { | |||||
Log.w( | |||||
"COMM", | |||||
"Notification payload JSON is not {[string]: string} type.", | |||||
e); | |||||
return; | |||||
} catch (Exception e) { | |||||
Log.w("COMM", "Failed to decrypt encrypted notification", e); | |||||
return; | |||||
} | |||||
} else if ("1".equals(message.getData().get(ENCRYPTION_FAILED_KEY))) { | |||||
Log.w( | |||||
"COMM", | |||||
"Received unencrypted notification for client with existing olm session for notifications"); | |||||
} | |||||
String badge = message.getData().get(BADGE_KEY); | String badge = message.getData().get(BADGE_KEY); | ||||
if (badge != null) { | if (badge != null) { | ||||
try { | try { | ||||
int badgeCount = Integer.parseInt(badge); | int badgeCount = Integer.parseInt(badge); | ||||
if (badgeCount > 0) { | if (badgeCount > 0) { | ||||
ShortcutBadger.applyCount(this, badgeCount); | ShortcutBadger.applyCount(this, badgeCount); | ||||
} else { | } else { | ||||
ShortcutBadger.removeCount(this); | ShortcutBadger.removeCount(this); | ||||
▲ Show 20 Lines • Show All 111 Lines • ▼ Show 20 Lines | private PendingIntent createStartMainActivityAction(RemoteMessage message) { | ||||
intent.putExtra("message", message); | intent.putExtra("message", message); | ||||
return PendingIntent.getActivity( | return PendingIntent.getActivity( | ||||
this.getApplicationContext(), | this.getApplicationContext(), | ||||
message.getData().get(NOTIF_ID_KEY).hashCode(), | message.getData().get(NOTIF_ID_KEY).hashCode(), | ||||
intent, | intent, | ||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); | PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); | ||||
} | } | ||||
private RemoteMessage decryptRemoteMessage(RemoteMessage message) | |||||
throws IOException, IllegalStateException { | |||||
String encryptedSerializedPayload = | |||||
message.getData().get(ENCRYPTED_PAYLOAD_KEY); | |||||
String decryptedSerializedPayload = NotificationsCryptoModule.decrypt( | |||||
encryptedSerializedPayload, | |||||
NotificationsCryptoModule.olmEncryptedTypeMessage(), | |||||
"CommNotificationsHandler"); | |||||
JsonReader decryptedPayloadReader = | |||||
new JsonReader(new StringReader(decryptedSerializedPayload)); | |||||
decryptedPayloadReader.beginObject(); | |||||
while (decryptedPayloadReader.hasNext()) { | |||||
String payloadFieldName = decryptedPayloadReader.nextName(); | |||||
String payloadFieldValue = decryptedPayloadReader.nextString(); | |||||
message.getData().put(payloadFieldName, payloadFieldValue); | |||||
} | |||||
decryptedPayloadReader.endObject(); | |||||
tomek: It is a really low-level API - can we find something that returns `Map` instead of iterating… | |||||
marcinAuthorUnsubmitted Done Inline Actions
We could use this library: https://www.baeldung.com/java-org-json, but we would need to add it to the project.
I have answered this question in one of the comments. Unfortunately it was lost during rebase + arc diff so I will quote it:
When we decide to create a nested structure in Android notification we can revisit this code, add org.json and change the implementation. The way this code is currently implemented will inform us that such a change has to be made. For the current use case I think this approach is fine. marcin: > It is a really low-level API - can we find something that returns Map instead of iterating… | |||||
tomekUnsubmitted Not Done Inline ActionsOk, makes sense, but having a warning on native client might not be enough to let us know about the issue. We should add some comments in the code that generates notifs that all the Android clients assume that notif is flat. tomek: Ok, makes sense, but having a warning on native client might not be enough to let us know about… | |||||
return message; | |||||
} | |||||
} | } |
It is a really low-level API - can we find something that returns Map instead of iterating through the fields or tokes? The problem with the current approach is that we're assuming that all the values are strings - is it always the case? Can't we send a notif where a prop is an object, or array? Supporting nested structure in this approach could be challenging.