- Change Android package from com.mattermost.rnbeta to com.tokilabs.mattermost (build.gradle, source files, AndroidManifest, google-services.json) - Change iOS bundle identifiers and app groups to com.tokilabs.mattermost (Info.plist, entitlements, project.pbxproj) - Update auth URL scheme from mmauthbeta to mmauth_tokilabs - Update all Fastlane env files and Fastfile references - Replace google-services.json with own Firebase project - Add .vscode/launch.json and settings.json for dev environment - Update .gitignore to track .vscode config files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
617 lines
25 KiB
Java
617 lines
25 KiB
Java
package com.mattermost.helpers;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.app.Notification;
|
|
import android.app.NotificationChannel;
|
|
import android.app.NotificationManager;
|
|
import android.app.PendingIntent;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.BitmapFactory;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
import android.graphics.Paint;
|
|
import android.graphics.PorterDuff;
|
|
import android.graphics.PorterDuffXfermode;
|
|
import android.graphics.Rect;
|
|
import android.graphics.RectF;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.text.TextUtils;
|
|
import android.util.Base64;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.core.app.NotificationCompat;
|
|
import androidx.core.app.NotificationManagerCompat;
|
|
import androidx.core.app.Person;
|
|
import androidx.core.app.RemoteInput;
|
|
import androidx.core.graphics.drawable.IconCompat;
|
|
|
|
import com.tokilabs.mattermost.*;
|
|
import com.mattermost.rnutils.helpers.NotificationHelper;
|
|
import com.nozbe.watermelondb.WMDatabase;
|
|
import com.mattermost.turbolog.TurboLog;
|
|
|
|
import java.io.IOException;
|
|
import java.security.KeyFactory;
|
|
import java.security.PublicKey;
|
|
import java.security.spec.X509EncodedKeySpec;
|
|
import java.util.Date;
|
|
import java.util.Objects;
|
|
|
|
import io.jsonwebtoken.IncorrectClaimException;
|
|
import io.jsonwebtoken.JwtException;
|
|
import io.jsonwebtoken.Jwts;
|
|
import io.jsonwebtoken.MissingClaimException;
|
|
import okhttp3.OkHttpClient;
|
|
import okhttp3.Request;
|
|
import okhttp3.Response;
|
|
|
|
import static com.mattermost.helpers.database_extension.GeneralKt.getDatabaseForServer;
|
|
import static com.mattermost.helpers.database_extension.GeneralKt.getDeviceToken;
|
|
import static com.mattermost.helpers.database_extension.SystemKt.queryConfigServerVersion;
|
|
import static com.mattermost.helpers.database_extension.SystemKt.queryConfigSigningKey;
|
|
import static com.mattermost.helpers.database_extension.UserKt.getLastPictureUpdate;
|
|
|
|
public class CustomPushNotificationHelper {
|
|
public static final String CHANNEL_HIGH_IMPORTANCE_ID = "channel_01";
|
|
public static final String CHANNEL_MIN_IMPORTANCE_ID = "channel_02";
|
|
public static final String KEY_TEXT_REPLY = "CAN_REPLY";
|
|
public static final String NOTIFICATION_ID = "notificationId";
|
|
public static final String NOTIFICATION = "notification";
|
|
public static final String PUSH_TYPE_MESSAGE = "message";
|
|
public static final String PUSH_TYPE_CLEAR = "clear";
|
|
public static final String PUSH_TYPE_SESSION = "session";
|
|
public static final String CATEGORY_CAN_REPLY = "CAN_REPLY";
|
|
|
|
private static NotificationChannel mHighImportanceChannel;
|
|
private static NotificationChannel mMinImportanceChannel;
|
|
|
|
private static final OkHttpClient client = new OkHttpClient();
|
|
|
|
private static final BitmapCache bitmapCache = new BitmapCache();
|
|
|
|
private static void addMessagingStyleMessages(Context context, NotificationCompat.MessagingStyle messagingStyle, String conversationTitle, Bundle bundle) {
|
|
String message = bundle.getString("message", bundle.getString("body"));
|
|
String senderId = bundle.getString("sender_id");
|
|
String serverUrl = bundle.getString("server_url");
|
|
String type = bundle.getString("type");
|
|
String urlOverride = bundle.getString("override_icon_url");
|
|
if (senderId == null) {
|
|
senderId = "sender_id";
|
|
}
|
|
String senderName = getSenderName(bundle);
|
|
|
|
if (conversationTitle == null || !android.text.TextUtils.isEmpty(senderName.trim())) {
|
|
message = removeSenderNameFromMessage(message, senderName);
|
|
}
|
|
|
|
long timestamp = new Date().getTime();
|
|
Person.Builder sender = new Person.Builder()
|
|
.setKey(senderId)
|
|
.setName(senderName);
|
|
|
|
if (serverUrl != null && type != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
|
|
try {
|
|
Bitmap avatar = userAvatar(context, serverUrl, senderId, urlOverride);
|
|
if (avatar != null) {
|
|
sender.setIcon(IconCompat.createWithBitmap(avatar));
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
messagingStyle.addMessage(message, timestamp, sender.build());
|
|
}
|
|
|
|
private static void addNotificationExtras(NotificationCompat.Builder notification, Bundle bundle) {
|
|
Bundle userInfoBundle = bundle.getBundle("userInfo");
|
|
if (userInfoBundle == null) {
|
|
userInfoBundle = new Bundle();
|
|
}
|
|
|
|
String channelId = bundle.getString("channel_id");
|
|
if (channelId != null) {
|
|
userInfoBundle.putString("channel_id", channelId);
|
|
}
|
|
|
|
String postId = bundle.getString("post_id");
|
|
if (postId != null) {
|
|
userInfoBundle.putString("post_id", postId);
|
|
}
|
|
|
|
String rootId = bundle.getString("root_id");
|
|
if (rootId != null) {
|
|
userInfoBundle.putString("root_id", rootId);
|
|
}
|
|
|
|
String crtEnabled = bundle.getString("is_crt_enabled");
|
|
if (crtEnabled != null) {
|
|
userInfoBundle.putString("is_crt_enabled", crtEnabled);
|
|
}
|
|
|
|
String serverUrl = bundle.getString("server_url");
|
|
if (serverUrl != null) {
|
|
userInfoBundle.putString("server_url", serverUrl);
|
|
}
|
|
|
|
notification.addExtras(userInfoBundle);
|
|
}
|
|
|
|
@SuppressLint("UnspecifiedImmutableFlag")
|
|
private static void addNotificationReplyAction(Context context, NotificationCompat.Builder notification, Bundle bundle, int notificationId) {
|
|
String postId = bundle.getString("post_id");
|
|
String serverUrl = bundle.getString("server_url");
|
|
boolean canReply = bundle.containsKey("category") && Objects.equals(bundle.getString("category"), CATEGORY_CAN_REPLY);
|
|
|
|
if (android.text.TextUtils.isEmpty(postId) || serverUrl == null || !canReply) {
|
|
return;
|
|
}
|
|
|
|
Intent replyIntent = new Intent(context, NotificationReplyBroadcastReceiver.class);
|
|
replyIntent.setAction(KEY_TEXT_REPLY);
|
|
replyIntent.putExtra(NOTIFICATION_ID, notificationId);
|
|
replyIntent.putExtra(NOTIFICATION, bundle);
|
|
|
|
PendingIntent replyPendingIntent;
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
replyPendingIntent = PendingIntent.getBroadcast(
|
|
context,
|
|
notificationId,
|
|
replyIntent,
|
|
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
|
} else {
|
|
replyPendingIntent = PendingIntent.getBroadcast(
|
|
context,
|
|
notificationId,
|
|
replyIntent,
|
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
|
}
|
|
|
|
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
|
|
.setLabel("Reply")
|
|
.build();
|
|
|
|
int icon = R.drawable.ic_notif_action_reply;
|
|
CharSequence title = "Reply";
|
|
NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(icon, title, replyPendingIntent)
|
|
.addRemoteInput(remoteInput)
|
|
.setAllowGeneratedReplies(true)
|
|
.build();
|
|
|
|
notification
|
|
.setShowWhen(true)
|
|
.addAction(replyAction);
|
|
}
|
|
|
|
public static NotificationCompat.Builder createNotificationBuilder(Context context, PendingIntent intent, Bundle bundle, boolean createSummary) {
|
|
final NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_HIGH_IMPORTANCE_ID);
|
|
|
|
String channelId = bundle.getString("channel_id");
|
|
String postId = bundle.getString("post_id");
|
|
String rootId = bundle.getString("root_id");
|
|
int notificationId = postId != null ? postId.hashCode() : NotificationHelper.INSTANCE.MESSAGE_NOTIFICATION_ID;
|
|
|
|
boolean is_crt_enabled = bundle.containsKey("is_crt_enabled") && Objects.equals(bundle.getString("is_crt_enabled"), "true");
|
|
String groupId = is_crt_enabled && !android.text.TextUtils.isEmpty(rootId) ? rootId : channelId;
|
|
|
|
addNotificationExtras(notification, bundle);
|
|
setNotificationIcons(context, notification, bundle);
|
|
setNotificationMessagingStyle(context, notification, bundle);
|
|
setNotificationGroup(notification, groupId, createSummary);
|
|
setNotificationBadgeType(notification);
|
|
|
|
setNotificationChannel(context, notification);
|
|
setNotificationDeleteIntent(context, notification, bundle, notificationId);
|
|
addNotificationReplyAction(context, notification, bundle, notificationId);
|
|
|
|
notification
|
|
.setContentIntent(intent)
|
|
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
|
|
.setPriority(Notification.PRIORITY_HIGH)
|
|
.setCategory(Notification.CATEGORY_MESSAGE)
|
|
.setAutoCancel(true);
|
|
|
|
return notification;
|
|
}
|
|
|
|
public static void createNotificationChannels(Context context) {
|
|
// Notification channels are not supported in Android Nougat and below
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
return;
|
|
}
|
|
|
|
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
|
|
|
if (mHighImportanceChannel == null) {
|
|
mHighImportanceChannel = new NotificationChannel(CHANNEL_HIGH_IMPORTANCE_ID, "High Importance", NotificationManager.IMPORTANCE_HIGH);
|
|
mHighImportanceChannel.setShowBadge(true);
|
|
notificationManager.createNotificationChannel(mHighImportanceChannel);
|
|
}
|
|
|
|
if (mMinImportanceChannel == null) {
|
|
mMinImportanceChannel = new NotificationChannel(CHANNEL_MIN_IMPORTANCE_ID, "Min Importance", NotificationManager.IMPORTANCE_MIN);
|
|
mMinImportanceChannel.setShowBadge(true);
|
|
notificationManager.createNotificationChannel(mMinImportanceChannel);
|
|
}
|
|
}
|
|
|
|
public static boolean verifySignature(final Context context, String signature, String serverUrl, String ackId) {
|
|
if (signature == null) {
|
|
// Backward compatibility with old push proxies
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", "No signature in the notification");
|
|
return true;
|
|
}
|
|
|
|
if (serverUrl == null) {
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", "No server_url for server_id");
|
|
return false;
|
|
}
|
|
|
|
DatabaseHelper dbHelper = DatabaseHelper.Companion.getInstance();
|
|
if (dbHelper == null) {
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", "Cannot access the database");
|
|
return false;
|
|
}
|
|
|
|
WMDatabase db = getDatabaseForServer(dbHelper, context, serverUrl);
|
|
if (db == null) {
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", "Cannot access the server database");
|
|
return false;
|
|
}
|
|
|
|
if (signature.equals("NO_SIGNATURE")) {
|
|
String version = queryConfigServerVersion(db);
|
|
if (version == null) {
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", "No server version");
|
|
return false;
|
|
}
|
|
|
|
if (!version.matches("[0-9]+(\\.[0-9]+)*")) {
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", "Invalid server version");
|
|
return false;
|
|
}
|
|
|
|
String[] parts = version.split("\\.");
|
|
int major = parts.length > 0 ? Integer.parseInt(parts[0]) : 0;
|
|
int minor = parts.length > 1 ? Integer.parseInt(parts[1]) : 0;
|
|
int patch = parts.length > 2 ? Integer.parseInt(parts[2]) : 0;
|
|
|
|
int[][] targets = {{9,8,0},{9,7,3},{9,6,3},{9,5,5},{8,1,14}};
|
|
boolean rejected = false;
|
|
for (int i = 0; i < targets.length; i++) {
|
|
boolean first = i == 0;
|
|
int[] targetVersion = targets[i];
|
|
int majorTarget = targetVersion[0];
|
|
int minorTarget = targetVersion[1];
|
|
int patchTarget = targetVersion[2];
|
|
|
|
if (major > majorTarget) {
|
|
// Only reject if we are considering the first (highest) version.
|
|
// Any version in between should be acceptable.
|
|
rejected = first;
|
|
break;
|
|
}
|
|
|
|
if (major < majorTarget) {
|
|
// Continue to see if it complies with a smaller target
|
|
continue;
|
|
}
|
|
|
|
// Same major
|
|
if (minor > minorTarget) {
|
|
// Only reject if we are considering the first (highest) version.
|
|
// Any version in between should be acceptable.
|
|
rejected = first;
|
|
break;
|
|
}
|
|
|
|
if (minor < minorTarget) {
|
|
// Continue to see if it complies with a smaller target
|
|
continue;
|
|
}
|
|
|
|
// Same major and same minor
|
|
if (patch >= patchTarget) {
|
|
rejected = true;
|
|
break;
|
|
}
|
|
|
|
// Patch is lower than target
|
|
return true;
|
|
}
|
|
|
|
if (rejected) {
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", "Server version should send signature");
|
|
return false;
|
|
}
|
|
|
|
// Version number is below any of the targets, so it should not send the signature
|
|
return true;
|
|
}
|
|
|
|
String signingKey = queryConfigSigningKey(db);
|
|
if (signingKey == null) {
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", "No signing key");
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
byte[] encoded = Base64.decode(signingKey, 0);
|
|
KeyFactory kf = KeyFactory.getInstance("EC");
|
|
PublicKey pubKey = (PublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
|
|
|
|
String storedDeviceToken = getDeviceToken(dbHelper);
|
|
if (storedDeviceToken == null) {
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", "No device token stored");
|
|
return false;
|
|
}
|
|
String[] tokenParts = storedDeviceToken.split(":", 2);
|
|
if (tokenParts.length != 2) {
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", "Wrong stored device token format");
|
|
return false;
|
|
}
|
|
String deviceToken = tokenParts[1].substring(0, tokenParts[1].length() -1 );
|
|
if (deviceToken.isEmpty()) {
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", "Empty stored device token");
|
|
return false;
|
|
}
|
|
|
|
Jwts.parser()
|
|
.require("ack_id", ackId)
|
|
.require("device_id", deviceToken)
|
|
.verifyWith((PublicKey) pubKey)
|
|
.build()
|
|
.parseSignedClaims(signature);
|
|
} catch (MissingClaimException e) {
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", String.format("Missing claim: %s", e.getMessage()));
|
|
e.printStackTrace();
|
|
return false;
|
|
} catch (IncorrectClaimException e) {
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", String.format("Incorrect claim: %s", e.getMessage()));
|
|
e.printStackTrace();
|
|
return false;
|
|
} catch (JwtException e) {
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", String.format("Cannot verify JWT: %s", e.getMessage()));
|
|
e.printStackTrace();
|
|
return false;
|
|
} catch (Exception e) {
|
|
TurboLog.Companion.i("Mattermost Notifications Signature verification", String.format("Exception while parsing JWT: %s", e.getMessage()));
|
|
e.printStackTrace();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static Bitmap getCircleBitmap(Bitmap bitmap) {
|
|
final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
|
|
bitmap.getHeight(), Bitmap.Config.ARGB_8888);
|
|
final Canvas canvas = new Canvas(output);
|
|
|
|
final int color = Color.RED;
|
|
final Paint paint = new Paint();
|
|
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
|
|
final RectF rectF = new RectF(rect);
|
|
|
|
paint.setAntiAlias(true);
|
|
canvas.drawARGB(0, 0, 0, 0);
|
|
paint.setColor(color);
|
|
canvas.drawOval(rectF, paint);
|
|
|
|
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
|
|
canvas.drawBitmap(bitmap, rect, rect, paint);
|
|
|
|
bitmap.recycle();
|
|
|
|
return output;
|
|
}
|
|
|
|
private static String getConversationTitle(Bundle bundle) {
|
|
String title = bundle.getString("channel_name");
|
|
|
|
if (android.text.TextUtils.isEmpty(title)) {
|
|
title = bundle.getString("sender_name");
|
|
}
|
|
|
|
if (android.text.TextUtils.isEmpty(title)) {
|
|
title = bundle.getString("title", "");
|
|
}
|
|
|
|
return title;
|
|
}
|
|
|
|
private static NotificationCompat.MessagingStyle getMessagingStyle(Context context, Bundle bundle) {
|
|
NotificationCompat.MessagingStyle messagingStyle;
|
|
final String senderId = "me";
|
|
final String serverUrl = bundle.getString("server_url");
|
|
final String type = bundle.getString("type");
|
|
String urlOverride = bundle.getString("override_icon_url");
|
|
|
|
Person.Builder sender = new Person.Builder()
|
|
.setKey(senderId)
|
|
.setName("Me");
|
|
|
|
if (serverUrl != null && type != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
|
|
try {
|
|
Bitmap avatar = userAvatar(context, serverUrl, "me", urlOverride);
|
|
if (avatar != null) {
|
|
sender.setIcon(IconCompat.createWithBitmap(avatar));
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
messagingStyle = new NotificationCompat.MessagingStyle(sender.build());
|
|
|
|
String conversationTitle = getConversationTitle(bundle);
|
|
setMessagingStyleConversationTitle(messagingStyle, conversationTitle, bundle);
|
|
addMessagingStyleMessages(context, messagingStyle, conversationTitle, bundle);
|
|
|
|
return messagingStyle;
|
|
}
|
|
|
|
private static String getSenderName(Bundle bundle) {
|
|
String senderName = bundle.getString("sender_name");
|
|
if (senderName != null) {
|
|
return senderName;
|
|
}
|
|
|
|
String channelName = bundle.getString("channel_name");
|
|
if (channelName != null && channelName.startsWith("@")) {
|
|
return channelName;
|
|
}
|
|
|
|
String message = bundle.getString("message");
|
|
if (message != null) {
|
|
String name = message.split(":")[0];
|
|
if (!name.equals(message)) {
|
|
return name;
|
|
}
|
|
}
|
|
|
|
return getConversationTitle(bundle);
|
|
}
|
|
|
|
private static String removeSenderNameFromMessage(String message, String senderName) {
|
|
int index = message.indexOf(senderName);
|
|
if (index == 0) {
|
|
message = message.substring(senderName.length());
|
|
}
|
|
|
|
return message.replaceFirst(": ", "").trim();
|
|
}
|
|
|
|
private static void setMessagingStyleConversationTitle(NotificationCompat.MessagingStyle messagingStyle, String conversationTitle, Bundle bundle) {
|
|
String channelName = getConversationTitle(bundle);
|
|
String senderName = bundle.getString("sender_name");
|
|
if (TextUtils.isEmpty(senderName)) {
|
|
senderName = getSenderName(bundle);
|
|
}
|
|
|
|
if (conversationTitle != null && !channelName.equals(senderName)) {
|
|
messagingStyle.setConversationTitle(conversationTitle);
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
messagingStyle.setGroupConversation(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void setNotificationBadgeType(NotificationCompat.Builder notification) {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
notification.setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE);
|
|
}
|
|
}
|
|
|
|
private static void setNotificationChannel(Context context, NotificationCompat.Builder notification) {
|
|
// If Android Oreo or above we need to register a channel
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
return;
|
|
}
|
|
|
|
if (mHighImportanceChannel == null) {
|
|
createNotificationChannels(context);
|
|
}
|
|
NotificationChannel notificationChannel = mHighImportanceChannel;
|
|
notification.setChannelId(notificationChannel.getId());
|
|
}
|
|
|
|
private static void setNotificationDeleteIntent(Context context, NotificationCompat.Builder notification, Bundle bundle, int notificationId) {
|
|
// Let's add a delete intent when the notification is dismissed
|
|
final String PUSH_NOTIFICATION_EXTRA_NAME = "pushNotification";
|
|
Intent delIntent = new Intent(context, NotificationDismissService.class);
|
|
delIntent.putExtra(NOTIFICATION_ID, notificationId);
|
|
delIntent.putExtra(PUSH_NOTIFICATION_EXTRA_NAME, bundle);
|
|
@SuppressLint("UnspecifiedImmutableFlag")
|
|
PendingIntent deleteIntent = PendingIntent.getService(context, (int) System.currentTimeMillis(), delIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
|
|
notification.setDeleteIntent(deleteIntent);
|
|
}
|
|
|
|
private static void setNotificationMessagingStyle(Context context, NotificationCompat.Builder notification, Bundle bundle) {
|
|
NotificationCompat.MessagingStyle messagingStyle = getMessagingStyle(context, bundle);
|
|
notification.setStyle(messagingStyle);
|
|
}
|
|
|
|
private static void setNotificationGroup(NotificationCompat.Builder notification, String channelId, boolean setAsSummary) {
|
|
notification.setGroup(channelId);
|
|
|
|
if (setAsSummary) {
|
|
// if this is the first notification for the channel then set as summary, otherwise skip
|
|
notification.setGroupSummary(true);
|
|
}
|
|
}
|
|
|
|
private static void setNotificationIcons(Context context, NotificationCompat.Builder notification, Bundle bundle) {
|
|
String channelName = getConversationTitle(bundle);
|
|
String senderName = bundle.getString("sender_name");
|
|
String serverUrl = bundle.getString("server_url");
|
|
String urlOverride = bundle.getString("override_icon_url");
|
|
|
|
notification.setSmallIcon(R.mipmap.ic_notification);
|
|
|
|
if (serverUrl != null && channelName.equals(senderName)) {
|
|
try {
|
|
String senderId = bundle.getString("sender_id");
|
|
Bitmap avatar = userAvatar(context, serverUrl, senderId, urlOverride);
|
|
if (avatar != null) {
|
|
notification.setLargeIcon(avatar);
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Bitmap userAvatar(final Context context, @NonNull final String serverUrl, final String userId, final String urlOverride) throws IOException {
|
|
try {
|
|
Response response;
|
|
Double lastUpdateAt = 0.0;
|
|
if (!TextUtils.isEmpty(urlOverride)) {
|
|
Request request = new Request.Builder().url(urlOverride).build();
|
|
TurboLog.Companion.i("ReactNative", String.format("Fetch override profile image %s", urlOverride));
|
|
response = client.newCall(request).execute();
|
|
} else {
|
|
DatabaseHelper dbHelper = DatabaseHelper.Companion.getInstance();
|
|
if (dbHelper != null) {
|
|
WMDatabase db = getDatabaseForServer(dbHelper, context, serverUrl);
|
|
if (db != null) {
|
|
lastUpdateAt = getLastPictureUpdate(db, userId);
|
|
if (lastUpdateAt == null) {
|
|
lastUpdateAt = 0.0;
|
|
}
|
|
db.close();
|
|
}
|
|
}
|
|
Bitmap cached = bitmapCache.bitmap(userId, lastUpdateAt, serverUrl);
|
|
if (cached != null) {
|
|
Bitmap bitmap = cached.copy(cached.getConfig(), false);
|
|
return getCircleBitmap(bitmap);
|
|
}
|
|
|
|
bitmapCache.removeBitmap(userId, serverUrl);
|
|
String url = String.format("api/v4/users/%s/image", userId);
|
|
TurboLog.Companion.i("ReactNative", String.format("Fetch profile image %s", url));
|
|
response = Network.getSync(serverUrl, url, null);
|
|
}
|
|
|
|
if (response.code() == 200) {
|
|
assert response.body() != null;
|
|
byte[] bytes = Objects.requireNonNull(response.body()).bytes();
|
|
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
|
if (TextUtils.isEmpty(urlOverride) && !TextUtils.isEmpty(userId)) {
|
|
bitmapCache.insertBitmap(bitmap.copy(bitmap.getConfig(), false), userId, lastUpdateAt, serverUrl);
|
|
}
|
|
return getCircleBitmap(bitmap);
|
|
}
|
|
|
|
return null;
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
return null;
|
|
}
|
|
}
|
|
}
|