Skip to content

Commit

Permalink
Merge branch 'main' into update-account-settings/preferences
Browse files Browse the repository at this point in the history
  • Loading branch information
kosmydel committed Feb 9, 2024
2 parents a33f3a8 + ed6f8e3 commit 5f13923
Show file tree
Hide file tree
Showing 39 changed files with 137 additions and 378 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,5 @@ public void onAirshipReady(@NonNull Context context, @NonNull UAirship airship)

CustomNotificationProvider notificationProvider = new CustomNotificationProvider(context, airship.getAirshipConfigOptions());
pushManager.setNotificationProvider(notificationProvider);

NotificationListener notificationListener = airship.getPushManager().getNotificationListener();
pushManager.setNotificationListener(new CustomNotificationListener(notificationListener, notificationProvider));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static androidx.core.app.NotificationCompat.CATEGORY_MESSAGE;
import static androidx.core.app.NotificationCompat.PRIORITY_MAX;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
Expand All @@ -15,6 +16,8 @@
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
Expand All @@ -26,6 +29,7 @@
import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.Person;
import androidx.core.graphics.drawable.IconCompat;
import androidx.versionedparcelable.ParcelUtils;

import com.urbanairship.AirshipConfigOptions;
import com.urbanairship.json.JsonMap;
Expand All @@ -40,18 +44,14 @@
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import com.expensify.chat.customairshipextender.NotificationCache.NotificationData;
import com.expensify.chat.customairshipextender.NotificationCache.NotificationMessage;

public class CustomNotificationProvider extends ReactNotificationProvider {
// Resize icons to 100 dp x 100 dp
private static final int MAX_ICON_SIZE_DPS = 100;
Expand All @@ -73,6 +73,13 @@ public class CustomNotificationProvider extends ReactNotificationProvider {
private static final String PAYLOAD_KEY = "payload";
private static final String ONYX_DATA_KEY = "onyxData";

// Notification extras keys
public static final String EXTRAS_REPORT_ID_KEY = "reportID";
public static final String EXTRAS_AVATAR_KEY = "avatar";
public static final String EXTRAS_NAME_KEY = "name";
public static final String EXTRAS_ACCOUNT_ID_KEY = "accountID";


private final ExecutorService executorService = Executors.newCachedThreadPool();

public CustomNotificationProvider(@NonNull Context context, @NonNull AirshipConfigOptions configOptions) {
Expand Down Expand Up @@ -158,7 +165,7 @@ public Bitmap getCroppedBitmap(Bitmap bitmap) {

/**
* Applies the message style to the notification builder. It also takes advantage of the
* notification cache to build conversations style notifications.
* android notification API to build conversations style notifications.
*
* @param builder Notification builder that will receive the message style
* @param payload Notification payload, which contains all the data we need to build the notifications.
Expand All @@ -170,10 +177,9 @@ private void applyMessageStyle(@NonNull Context context, NotificationCompat.Buil
return;
}

// Retrieve and check for cached notifications
NotificationData notificationData = NotificationCache.getNotificationData(reportID);
boolean hasExistingNotification = notificationData.messages.size() >= 1;

// Retrieve and check for existing notifications
StatusBarNotification existingReportNotification = getActiveNotificationByReportId(context, reportID);
boolean hasExistingNotification = existingReportNotification != null;
try {
JsonMap reportMap = payload.get(ONYX_DATA_KEY).getList().get(1).getMap().get("value").getMap();
String reportId = reportMap.keySet().iterator().next();
Expand All @@ -187,31 +193,15 @@ private void applyMessageStyle(@NonNull Context context, NotificationCompat.Buil
String message = alert != null ? alert : messageData.get("message").getList().get(0).getMap().get("text").getString();
String conversationName = payload.get("roomName") == null ? "" : payload.get("roomName").getString("");

// Retrieve or create the Person object who sent the latest report comment
Person person = notificationData.getPerson(accountID);
Bitmap personIcon = notificationData.getIcon(accountID);

if (personIcon == null) {
personIcon = fetchIcon(context, avatar);
}
// create the Person object who sent the latest report comment
Bitmap personIcon = fetchIcon(context, avatar);
builder.setLargeIcon(personIcon);

// Persist the person and icon to the notification cache
if (person == null) {
IconCompat iconCompat = IconCompat.createWithBitmap(personIcon);
person = new Person.Builder()
.setIcon(iconCompat)
.setKey(accountID)
.setName(name)
.build();

notificationData.putPerson(accountID, name, personIcon);
}
Person person = createMessagePersonObject(IconCompat.createWithBitmap(personIcon), accountID, name);

// Despite not using conversation style for the initial notification from each chat, we need to cache it to enable conversation style for future notifications
// Create latest received message object
long createdTimeInMillis = getMessageTimeInMillis(messageData.get("created").getString(""));
notificationData.messages.add(new NotificationMessage(accountID, message, createdTimeInMillis));

NotificationCompat.MessagingStyle.Message newMessage = new NotificationCompat.MessagingStyle.Message(message, createdTimeInMillis, person);

// Conversational styling should be applied to groups chats, rooms, and any 1:1 chats with more than one notification (ensuring the large profile image is always shown)
if (!conversationName.isEmpty() || hasExistingNotification) {
Expand All @@ -220,30 +210,77 @@ private void applyMessageStyle(@NonNull Context context, NotificationCompat.Buil
.setGroupConversation(true)
.setConversationTitle(conversationName);


// Add all conversation messages to the notification, including the last one we just received.
for (NotificationMessage cachedMessage : notificationData.messages) {
messagingStyle.addMessage(cachedMessage.text, cachedMessage.time, notificationData.getPerson(cachedMessage.accountID));
List<NotificationCompat.MessagingStyle.Message> messages;
if (hasExistingNotification) {
NotificationCompat.MessagingStyle previousStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(existingReportNotification.getNotification());
messages = previousStyle != null ? previousStyle.getMessages() : new ArrayList<>(List.of(recreatePreviousMessage(existingReportNotification)));
} else {
messages = new ArrayList<>();
}

// add the last one message we just received.
messages.add(newMessage);

for (NotificationCompat.MessagingStyle.Message activeMessage : messages) {
messagingStyle.addMessage(activeMessage);
}

builder.setStyle(messagingStyle);
}

// save reportID and person info for future merging
builder.addExtras(createMessageExtrasBundle(reportID, person));

// Clear the previous notification associated to this conversation so it looks like we are
// replacing them with this new one we just built.
if (notificationData.prevNotificationID != -1) {
NotificationManagerCompat.from(context).cancel(notificationData.prevNotificationID);
if (hasExistingNotification) {
int previousNotificationID = existingReportNotification.getId();
NotificationManagerCompat.from(context).cancel(previousNotificationID);
}

} catch (Exception e) {
e.printStackTrace();
}
}

private Person createMessagePersonObject (IconCompat icon, String key, String name) {
return new Person.Builder().setIcon(icon).setKey(key).setName(name).build();
}

private NotificationCompat.MessagingStyle.Message recreatePreviousMessage (StatusBarNotification statusBarNotification) {
// Get previous message
Notification previousNotification = statusBarNotification.getNotification();
String previousMessage = previousNotification.extras.getString("android.text");
long time = statusBarNotification.getNotification().when;
// Recreate Person object
IconCompat avatarBitmap = ParcelUtils.getVersionedParcelable(previousNotification.extras, EXTRAS_AVATAR_KEY);
String previousName = previousNotification.extras.getString(EXTRAS_NAME_KEY);
String previousAccountID = previousNotification.extras.getString(EXTRAS_ACCOUNT_ID_KEY);
Person previousPerson = createMessagePersonObject(avatarBitmap, previousAccountID, previousName);

return new NotificationCompat.MessagingStyle.Message(previousMessage, time, previousPerson);
}

// Store the new notification ID so we can replace the notification if this conversation
// receives more messages
notificationData.prevNotificationID = notificationID;
private Bundle createMessageExtrasBundle(long reportID, Person person) {
Bundle extrasBundle = new Bundle();
extrasBundle.putLong(EXTRAS_REPORT_ID_KEY, reportID);
ParcelUtils.putVersionedParcelable(extrasBundle, EXTRAS_AVATAR_KEY, person.getIcon());
extrasBundle.putString(EXTRAS_ACCOUNT_ID_KEY, person.getKey());
extrasBundle.putString(EXTRAS_NAME_KEY, person.getName().toString());

NotificationCache.setNotificationData(reportID, notificationData);
return extrasBundle;
}

private StatusBarNotification getActiveNotificationByReportId(@NonNull Context context, long reportId) {
List<StatusBarNotification> notifications = NotificationManagerCompat.from(context).getActiveNotifications();
for (StatusBarNotification currentNotification : notifications) {
long associatedReportId = currentNotification.getNotification().extras.getLong("reportID", -1);
if (associatedReportId == reportId) return currentNotification;
}
return null;
}
/**
* Safely retrieve the message time in milliseconds
*/
Expand All @@ -260,26 +297,6 @@ private long getMessageTimeInMillis(String createdTime) {
return Calendar.getInstance().getTimeInMillis();
}

/**
* Remove the notification data from the cache when the user dismisses the notification.
*
* @param message Push notification's message
*/
public void onDismissNotification(PushMessage message) {
try {
JsonMap payload = JsonValue.parseString(message.getExtra(PAYLOAD_KEY)).optMap();
long reportID = payload.get("reportID").getLong(-1);

if (reportID == -1) {
return;
}

NotificationCache.setNotificationData(reportID, null);
} catch (Exception e) {
Log.e(TAG, "Failed to delete conversation cache. SendID=" + message.getSendId(), e);
}
}

private Bitmap fetchIcon(@NonNull Context context, String urlString) {
URL parsedUrl = null;
try {
Expand Down
Loading

0 comments on commit 5f13923

Please sign in to comment.