From 23d166146e7b4586d84a18fb07769a1ec301f606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Tue, 27 Jul 2021 18:32:18 +0200 Subject: [PATCH 01/16] Start using the messaging style for the notifications --- android/app/src/main/AndroidManifest.xml | 3 + .../expensify/chat/CustomAirshipExtender.java | 15 ++ .../chat/CustomNotificationProvider.java | 249 ++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java create mode 100644 android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6976c0f8c828..1ee7203fb56e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -59,5 +59,8 @@ + + diff --git a/android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java b/android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java new file mode 100644 index 000000000000..76f024c1c749 --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java @@ -0,0 +1,15 @@ +package com.expensify.chat; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.urbanairship.UAirship; +import com.urbanairship.reactnative.AirshipExtender; + +public class CustomAirshipExtender implements AirshipExtender { + @Override + public void onAirshipReady(@NonNull Context context, @NonNull UAirship airship) { + airship.getPushManager().setNotificationProvider(new CustomNotificationProvider(context, airship.getAirshipConfigOptions())); + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java new file mode 100644 index 000000000000..0200a10cf2ea --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java @@ -0,0 +1,249 @@ +package com.expensify.chat; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.graphics.Bitmap; +import android.os.Build; +import android.service.notification.StatusBarNotification; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.view.WindowManager; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import androidx.core.app.NotificationCompat; +import androidx.core.app.Person; +import androidx.core.graphics.drawable.IconCompat; + +import com.urbanairship.AirshipConfigOptions; +import com.urbanairship.json.JsonException; +import com.urbanairship.json.JsonList; +import com.urbanairship.json.JsonMap; +import com.urbanairship.json.JsonValue; +import com.urbanairship.push.PushMessage; +import com.urbanairship.push.notifications.NotificationArguments; +import com.urbanairship.reactnative.ReactNotificationProvider; +import com.urbanairship.util.ImageUtils; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +public class CustomNotificationProvider extends ReactNotificationProvider { + + // Resize icons to 100 dp x 100 dp + private static final int MAX_ICON_SIZE_DPS = 100; + + // Max wait time to resolve an icon. We have ~10 seconds to a little less + // to ensure the notification builds. + private static final int MAX_ICON_FETCH_WAIT_TIME_SECONDS = 8; + + // Fallback drawable ID. 0 to not use a fallback ID. + private static final int FALLBACK_ICON_ID = 0; + + // Logging + private static final String TAG = "NotificationProvider"; + + // Conversation JSON keys + private static final String CONVERSATION_KEY = "conversation"; + private static final String CONVERSATION_OWNER_KEY = "owner"; + private static final String CONVERSATION_TITLE_KEY = "title"; + private static final String PEOPLE_KEY = "people"; + private static final String PERSON_ID_KEY = "id"; + private static final String PERSON_ICON_KEY = "icon"; + private static final String PERSON_NAME_KEY = "name"; + private static final String MESSAGES_KEY = "messages"; + private static final String MESSAGE_TEXT_KEY = "text"; + private static final String MESSAGE_TIME_KEY = "time"; + private static final String MESSAGE_PERSON_KEY = "person"; + + // Expensify Conversation JSON keys + private static final String PAYLOAD_KEY = "payload"; + private static final String TYPE_KEY = "type"; + private static final String REPORT_COMMENT_TYPE = "reportComment"; + + private final ExecutorService executorService = Executors.newCachedThreadPool(); + + public CustomNotificationProvider(@NonNull Context context, @NonNull AirshipConfigOptions configOptions) { + super(context, configOptions); + } + + @NonNull + @Override + protected NotificationCompat.Builder onExtendBuilder(@NonNull Context context, @NonNull NotificationCompat.Builder builder, @NonNull NotificationArguments arguments) { + super.onExtendBuilder(context, builder, arguments); + PushMessage message = arguments.getMessage(); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return builder; + } + + if (message.containsKey(CONVERSATION_KEY)) { + applyMessageStyle(message, builder); + } + + if (message.containsKey(PAYLOAD_KEY)) { + try { + JsonMap payload = JsonValue.parseString(message.getExtra(PAYLOAD_KEY)).optMap(); + + if (REPORT_COMMENT_TYPE.equals(payload.get(TYPE_KEY).getString())) { + applyExpensifyMessageStyle(builder, payload); + } + } catch (Exception e) { + Log.e(TAG, "Failed to parse conversation", e); + } + } + + return builder; + } + + private void applyExpensifyMessageStyle(NotificationCompat.Builder builder, JsonMap payload) { + int reportID = payload.get("reportID").getInt(-1); + JsonMap reportAction = payload.get("reportAction").getMap(); + String name = reportAction.get("person").getList().get(0).getMap().get("text").getString(); + String avatar = reportAction.get("avatar").getString(); + String accountID = Integer.toString(reportAction.get("actorAccountID").getInt(-1)); + + String message = reportAction.get("message").getList().get(0).getMap().get("text").getString(); + long time = reportAction.get("timestamp").getLong(0); + + IconCompat iconCompat = fetchIcon(avatar, FALLBACK_ICON_ID); + Person person = new Person.Builder() + .setIcon(iconCompat) + .setKey(accountID) + .setName(name) + .build(); + + NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(person) + .setGroupConversation(false) + .setConversationTitle("Chat with " + name); + + messagingStyle.addMessage(message, time, person); + + builder.setStyle(messagingStyle); + } + + + private void applyMessageStyle(PushMessage message, NotificationCompat.Builder builder) { + JsonMap conversation = null; + try { + conversation = JsonValue.parseString(message.getExtra(CONVERSATION_KEY)).optMap(); + } catch (JsonException e) { + Log.e(TAG, "Failed to parse conversation", e); + } + + if (conversation == null) { + return; + } + + Map people = resolvePeople(conversation.opt(PEOPLE_KEY).optList()); + if (people.isEmpty()) { + Log.e(TAG, "Missing people."); + return; + } + + String ownerKey = conversation.get(CONVERSATION_OWNER_KEY).getString(); + if (!people.containsKey(ownerKey)) { + Log.e(TAG, "Missing owner."); + return; + } + + NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(people.get(ownerKey)) + .setGroupConversation(people.size() > 2) + .setConversationTitle(conversation.opt(CONVERSATION_TITLE_KEY).optString()); + + for (JsonValue messageJson : conversation.opt(MESSAGES_KEY).optList()) { + String personKey = messageJson.optMap().opt(MESSAGE_PERSON_KEY).getString(); + String text = messageJson.optMap().opt(MESSAGE_TEXT_KEY).getString(); + long time = messageJson.optMap().opt(MESSAGE_TIME_KEY).getLong(0); + + if (people.containsKey(personKey) && text != null && time > 0) { + messagingStyle.addMessage(text, time, people.get(personKey)); + } + } + + builder.setStyle(messagingStyle); + } + + private Map resolvePeople(JsonList peopleJson) { + Map people = Collections.synchronizedMap(new HashMap<>()); + CountDownLatch countDownLatch = new CountDownLatch(peopleJson.size()); + + for (JsonValue personJson : peopleJson) { + executorService.execute(() -> { + String id = personJson.optMap().opt(PERSON_ID_KEY).optString(); + String name = personJson.optMap().opt(PERSON_NAME_KEY).optString(); + String icon = personJson.optMap().opt(PERSON_ICON_KEY).optString(); + + if (id != null) { + IconCompat iconCompat = fetchIcon(icon, FALLBACK_ICON_ID); + Person person = new Person.Builder() + .setIcon(iconCompat) + .setKey(id) + .setName(name) + .build(); + + people.put(id, person); + } + + countDownLatch.countDown(); + }); + } + + try { + countDownLatch.await(); + } catch (InterruptedException e) { + Log.e(TAG, "Failed to resolve people", e); + Thread.currentThread().interrupt(); + } + + return people; + } + + @NonNull + private IconCompat fetchIcon(@NonNull String urlString, @DrawableRes int fallbackId) { + // TODO: Add disk LRU cache + + URL parsedUrl = null; + try { + parsedUrl = urlString == null ? null : new URL(urlString); + } catch (MalformedURLException e) { + Log.e(TAG, "Failed to resolve URL " + urlString, e); + } + + if (parsedUrl != null) { + WindowManager window = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics dm = new DisplayMetrics(); + window.getDefaultDisplay().getMetrics(dm); + + final int reqWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, MAX_ICON_SIZE_DPS, dm); + final int reqHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, MAX_ICON_SIZE_DPS, dm); + + final URL url = parsedUrl; + Future future = executorService.submit(() -> ImageUtils.fetchScaledBitmap(context, url, reqWidth, reqHeight)); + + try { + Bitmap bitmap = future.get(MAX_ICON_FETCH_WAIT_TIME_SECONDS, TimeUnit.SECONDS); + return IconCompat.createWithBitmap(bitmap); + } catch (InterruptedException e) { + Log.e(TAG,"Failed to fetch icon", e); + Thread.currentThread().interrupt(); + } catch (Exception e) { + Log.e(TAG,"Failed to fetch icon", e); + future.cancel(true); + } + } + + return fallbackId == 0 ? null : IconCompat.createWithResource(context, fallbackId); + } +} From c3fc83e3cd3a891ea69b810b128dc85c64f6c6fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Wed, 28 Jul 2021 12:35:59 +0200 Subject: [PATCH 02/16] Cache conversations to update update conversations --- .../chat/CustomNotificationProvider.java | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java index 0200a10cf2ea..248d65269df3 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java @@ -15,6 +15,7 @@ import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; import androidx.core.app.Person; import androidx.core.graphics.drawable.IconCompat; @@ -30,6 +31,7 @@ import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -73,6 +75,7 @@ public class CustomNotificationProvider extends ReactNotificationProvider { private static final String REPORT_COMMENT_TYPE = "reportComment"; private final ExecutorService executorService = Executors.newCachedThreadPool(); + private final HashMap cache = new HashMap<>(); public CustomNotificationProvider(@NonNull Context context, @NonNull AirshipConfigOptions configOptions) { super(context, configOptions); @@ -97,7 +100,7 @@ protected NotificationCompat.Builder onExtendBuilder(@NonNull Context context, @ JsonMap payload = JsonValue.parseString(message.getExtra(PAYLOAD_KEY)).optMap(); if (REPORT_COMMENT_TYPE.equals(payload.get(TYPE_KEY).getString())) { - applyExpensifyMessageStyle(builder, payload); + applyExpensifyMessageStyle(builder, payload, arguments); } } catch (Exception e) { Log.e(TAG, "Failed to parse conversation", e); @@ -107,8 +110,10 @@ protected NotificationCompat.Builder onExtendBuilder(@NonNull Context context, @ return builder; } - private void applyExpensifyMessageStyle(NotificationCompat.Builder builder, JsonMap payload) { + private void applyExpensifyMessageStyle(NotificationCompat.Builder builder, JsonMap payload, NotificationArguments arguments) { int reportID = payload.get("reportID").getInt(-1); + NotificationCache notificationCache = findOrCreateNotificationCache(reportID); + JsonMap reportAction = payload.get("reportAction").getMap(); String name = reportAction.get("person").getList().get(0).getMap().get("text").getString(); String avatar = reportAction.get("avatar").getString(); @@ -124,13 +129,37 @@ private void applyExpensifyMessageStyle(NotificationCompat.Builder builder, Json .setName(name) .build(); + if (!notificationCache.people.containsKey(accountID)) { + notificationCache.people.put(accountID, person); + } + + notificationCache.messages.add(new NotificationCache.Message(person, message, time)); + NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(person) - .setGroupConversation(false) + .setGroupConversation(notificationCache.people.size() > 2) .setConversationTitle("Chat with " + name); - messagingStyle.addMessage(message, time, person); + for (NotificationCache.Message cachedMessage : notificationCache.messages) { + messagingStyle.addMessage(cachedMessage.text, cachedMessage.time, cachedMessage.person); + } + + if (notificationCache.prevNotificationId != -1) { + NotificationManagerCompat.from(context).cancel(notificationCache.prevNotificationId); + } builder.setStyle(messagingStyle); + notificationCache.prevNotificationId = arguments.getNotificationId(); + } + + private NotificationCache findOrCreateNotificationCache(int reportID) { + NotificationCache notificationCache = cache.get(reportID); + + if (notificationCache == null) { + notificationCache = new NotificationCache(); + cache.put(reportID, notificationCache); + } + + return notificationCache; } @@ -246,4 +275,22 @@ private IconCompat fetchIcon(@NonNull String urlString, @DrawableRes int fallbac return fallbackId == 0 ? null : IconCompat.createWithResource(context, fallbackId); } + + private static class NotificationCache { + public Map people = new HashMap<>(); + public ArrayList messages = new ArrayList<>(); + public int prevNotificationId = -1; + + public static class Message { + public Person person; + public String text; + public long time; + + Message(Person person, String text, long time) { + this.person = person; + this.text = text; + this.time = time; + } + } + } } From 259c99303d8df1cb2eca1cc856931eb259417d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Wed, 28 Jul 2021 14:03:06 +0200 Subject: [PATCH 03/16] Clear cache on notification dismiss --- .../expensify/chat/CustomAirshipExtender.java | 10 +- .../chat/CustomNotificationListener.java | 48 ++++++++ .../chat/CustomNotificationProvider.java | 111 ++---------------- 3 files changed, 68 insertions(+), 101 deletions(-) create mode 100644 android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java diff --git a/android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java b/android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java index 76f024c1c749..32fa546102cd 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java +++ b/android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java @@ -5,11 +5,19 @@ import androidx.annotation.NonNull; import com.urbanairship.UAirship; +import com.urbanairship.push.NotificationListener; +import com.urbanairship.push.PushManager; import com.urbanairship.reactnative.AirshipExtender; public class CustomAirshipExtender implements AirshipExtender { @Override public void onAirshipReady(@NonNull Context context, @NonNull UAirship airship) { - airship.getPushManager().setNotificationProvider(new CustomNotificationProvider(context, airship.getAirshipConfigOptions())); + PushManager pushManager = airship.getPushManager(); + + CustomNotificationProvider notificationProvider = new CustomNotificationProvider(context, airship.getAirshipConfigOptions()); + pushManager.setNotificationProvider(notificationProvider); + + NotificationListener notificationListener = airship.getPushManager().getNotificationListener(); + pushManager.setNotificationListener(new CustomNotificationListener(notificationListener, notificationProvider)); } } \ No newline at end of file diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java new file mode 100644 index 000000000000..7d00e74b542c --- /dev/null +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java @@ -0,0 +1,48 @@ +package com.expensify.chat; + +import androidx.annotation.NonNull; + +import com.urbanairship.push.NotificationActionButtonInfo; +import com.urbanairship.push.NotificationInfo; +import com.urbanairship.push.NotificationListener; +import com.urbanairship.push.PushMessage; + +import org.jetbrains.annotations.NotNull; + +public class CustomNotificationListener implements NotificationListener { + private final NotificationListener parent; + private final CustomNotificationProvider provider; + + CustomNotificationListener(NotificationListener parent, CustomNotificationProvider provider) { + this.parent = parent; + this.provider = provider; + } + + @Override + public void onNotificationPosted(@NonNull @NotNull NotificationInfo notificationInfo) { + parent.onNotificationPosted(notificationInfo); + } + + @Override + public boolean onNotificationOpened(@NonNull @NotNull NotificationInfo notificationInfo) { + return parent.onNotificationOpened(notificationInfo); + } + + @Override + public boolean onNotificationForegroundAction(@NonNull @NotNull NotificationInfo notificationInfo, @NonNull @NotNull NotificationActionButtonInfo actionButtonInfo) { + return parent.onNotificationForegroundAction(notificationInfo, actionButtonInfo); + } + + @Override + public void onNotificationBackgroundAction(@NonNull @NotNull NotificationInfo notificationInfo, @NonNull @NotNull NotificationActionButtonInfo actionButtonInfo) { + parent.onNotificationBackgroundAction(notificationInfo, actionButtonInfo); + } + + @Override + public void onNotificationDismissed(@NonNull @NotNull NotificationInfo notificationInfo) { + parent.onNotificationDismissed(notificationInfo); + + PushMessage message = notificationInfo.getMessage(); + provider.onDismissNotification(message); + } +} diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java index 248d65269df3..db2f8f711d5b 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java @@ -1,11 +1,8 @@ package com.expensify.chat; -import android.app.Notification; -import android.app.NotificationManager; import android.content.Context; import android.graphics.Bitmap; import android.os.Build; -import android.service.notification.StatusBarNotification; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; @@ -13,15 +10,12 @@ import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.app.Person; import androidx.core.graphics.drawable.IconCompat; import com.urbanairship.AirshipConfigOptions; -import com.urbanairship.json.JsonException; -import com.urbanairship.json.JsonList; import com.urbanairship.json.JsonMap; import com.urbanairship.json.JsonValue; import com.urbanairship.push.PushMessage; @@ -32,10 +26,8 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -57,25 +49,12 @@ public class CustomNotificationProvider extends ReactNotificationProvider { private static final String TAG = "NotificationProvider"; // Conversation JSON keys - private static final String CONVERSATION_KEY = "conversation"; - private static final String CONVERSATION_OWNER_KEY = "owner"; - private static final String CONVERSATION_TITLE_KEY = "title"; - private static final String PEOPLE_KEY = "people"; - private static final String PERSON_ID_KEY = "id"; - private static final String PERSON_ICON_KEY = "icon"; - private static final String PERSON_NAME_KEY = "name"; - private static final String MESSAGES_KEY = "messages"; - private static final String MESSAGE_TEXT_KEY = "text"; - private static final String MESSAGE_TIME_KEY = "time"; - private static final String MESSAGE_PERSON_KEY = "person"; - - // Expensify Conversation JSON keys private static final String PAYLOAD_KEY = "payload"; private static final String TYPE_KEY = "type"; private static final String REPORT_COMMENT_TYPE = "reportComment"; private final ExecutorService executorService = Executors.newCachedThreadPool(); - private final HashMap cache = new HashMap<>(); + public final HashMap cache = new HashMap<>(); public CustomNotificationProvider(@NonNull Context context, @NonNull AirshipConfigOptions configOptions) { super(context, configOptions); @@ -91,16 +70,12 @@ protected NotificationCompat.Builder onExtendBuilder(@NonNull Context context, @ return builder; } - if (message.containsKey(CONVERSATION_KEY)) { - applyMessageStyle(message, builder); - } - if (message.containsKey(PAYLOAD_KEY)) { try { JsonMap payload = JsonValue.parseString(message.getExtra(PAYLOAD_KEY)).optMap(); if (REPORT_COMMENT_TYPE.equals(payload.get(TYPE_KEY).getString())) { - applyExpensifyMessageStyle(builder, payload, arguments); + applyMessageStyle(builder, payload, arguments); } } catch (Exception e) { Log.e(TAG, "Failed to parse conversation", e); @@ -110,15 +85,13 @@ protected NotificationCompat.Builder onExtendBuilder(@NonNull Context context, @ return builder; } - private void applyExpensifyMessageStyle(NotificationCompat.Builder builder, JsonMap payload, NotificationArguments arguments) { + private void applyMessageStyle(NotificationCompat.Builder builder, JsonMap payload, NotificationArguments arguments) { int reportID = payload.get("reportID").getInt(-1); NotificationCache notificationCache = findOrCreateNotificationCache(reportID); - JsonMap reportAction = payload.get("reportAction").getMap(); String name = reportAction.get("person").getList().get(0).getMap().get("text").getString(); String avatar = reportAction.get("avatar").getString(); String accountID = Integer.toString(reportAction.get("actorAccountID").getInt(-1)); - String message = reportAction.get("message").getList().get(0).getMap().get("text").getString(); long time = reportAction.get("timestamp").getLong(0); @@ -162,81 +135,19 @@ private NotificationCache findOrCreateNotificationCache(int reportID) { return notificationCache; } - - private void applyMessageStyle(PushMessage message, NotificationCompat.Builder builder) { - JsonMap conversation = null; + public void onDismissNotification(PushMessage message) { try { - conversation = JsonValue.parseString(message.getExtra(CONVERSATION_KEY)).optMap(); - } catch (JsonException e) { - Log.e(TAG, "Failed to parse conversation", e); - } - - if (conversation == null) { - return; - } - - Map people = resolvePeople(conversation.opt(PEOPLE_KEY).optList()); - if (people.isEmpty()) { - Log.e(TAG, "Missing people."); - return; - } - - String ownerKey = conversation.get(CONVERSATION_OWNER_KEY).getString(); - if (!people.containsKey(ownerKey)) { - Log.e(TAG, "Missing owner."); - return; - } + JsonMap payload = JsonValue.parseString(message.getExtra(PAYLOAD_KEY)).optMap(); + int reportID = payload.get("reportID").getInt(-1); - NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(people.get(ownerKey)) - .setGroupConversation(people.size() > 2) - .setConversationTitle(conversation.opt(CONVERSATION_TITLE_KEY).optString()); - - for (JsonValue messageJson : conversation.opt(MESSAGES_KEY).optList()) { - String personKey = messageJson.optMap().opt(MESSAGE_PERSON_KEY).getString(); - String text = messageJson.optMap().opt(MESSAGE_TEXT_KEY).getString(); - long time = messageJson.optMap().opt(MESSAGE_TIME_KEY).getLong(0); - - if (people.containsKey(personKey) && text != null && time > 0) { - messagingStyle.addMessage(text, time, people.get(personKey)); + if (reportID == -1) { + return; } - } - - builder.setStyle(messagingStyle); - } - private Map resolvePeople(JsonList peopleJson) { - Map people = Collections.synchronizedMap(new HashMap<>()); - CountDownLatch countDownLatch = new CountDownLatch(peopleJson.size()); - - for (JsonValue personJson : peopleJson) { - executorService.execute(() -> { - String id = personJson.optMap().opt(PERSON_ID_KEY).optString(); - String name = personJson.optMap().opt(PERSON_NAME_KEY).optString(); - String icon = personJson.optMap().opt(PERSON_ICON_KEY).optString(); - - if (id != null) { - IconCompat iconCompat = fetchIcon(icon, FALLBACK_ICON_ID); - Person person = new Person.Builder() - .setIcon(iconCompat) - .setKey(id) - .setName(name) - .build(); - - people.put(id, person); - } - - countDownLatch.countDown(); - }); - } - - try { - countDownLatch.await(); - } catch (InterruptedException e) { - Log.e(TAG, "Failed to resolve people", e); - Thread.currentThread().interrupt(); + cache.remove(reportID); + } catch (Exception e) { + Log.e(TAG, "Failed to delete conversation cache"); } - - return people; } @NonNull From c00d7d9b6239ba27d347266f0e05a2cf8d4b7758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Wed, 28 Jul 2021 16:41:05 +0200 Subject: [PATCH 04/16] Add some comments --- .../chat/CustomNotificationListener.java | 3 ++ .../chat/CustomNotificationProvider.java | 34 +++++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java index 7d00e74b542c..73d8a1031654 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java @@ -9,6 +9,9 @@ import org.jetbrains.annotations.NotNull; +/** + * Allows us to clear the notification cache when the user dismisses a notification. + */ public class CustomNotificationListener implements NotificationListener { private final NotificationListener parent; private final CustomNotificationProvider provider; diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java index db2f8f711d5b..35b56b629dd9 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java @@ -74,8 +74,9 @@ protected NotificationCompat.Builder onExtendBuilder(@NonNull Context context, @ try { JsonMap payload = JsonValue.parseString(message.getExtra(PAYLOAD_KEY)).optMap(); + // Apply message style only for report comments if (REPORT_COMMENT_TYPE.equals(payload.get(TYPE_KEY).getString())) { - applyMessageStyle(builder, payload, arguments); + applyMessageStyle(builder, payload, arguments.getNotificationId()); } } catch (Exception e) { Log.e(TAG, "Failed to parse conversation", e); @@ -85,7 +86,15 @@ protected NotificationCompat.Builder onExtendBuilder(@NonNull Context context, @ return builder; } - private void applyMessageStyle(NotificationCompat.Builder builder, JsonMap payload, NotificationArguments arguments) { + /** + * Applies the message style to the notification builder. It also takes advantage of the + * notification cache to build conversations. + * + * @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. + * @param notificationID Current notification ID + */ + private void applyMessageStyle(NotificationCompat.Builder builder, JsonMap payload, int notificationID) { int reportID = payload.get("reportID").getInt(-1); NotificationCache notificationCache = findOrCreateNotificationCache(reportID); JsonMap reportAction = payload.get("reportAction").getMap(); @@ -116,14 +125,22 @@ private void applyMessageStyle(NotificationCompat.Builder builder, JsonMap paylo messagingStyle.addMessage(cachedMessage.text, cachedMessage.time, cachedMessage.person); } - if (notificationCache.prevNotificationId != -1) { - NotificationManagerCompat.from(context).cancel(notificationCache.prevNotificationId); + if (notificationCache.prevNotificationID != -1) { + NotificationManagerCompat.from(context).cancel(notificationCache.prevNotificationID); } builder.setStyle(messagingStyle); - notificationCache.prevNotificationId = arguments.getNotificationId(); + notificationCache.prevNotificationID = notificationID; } + /** + * Check if we are showing a notification related to a reportID. + * If not, create a new NotificationCache so we can build a conversation notification + * as the messages come. + * + * @param reportID Report ID. + * @return Notification Cache. + */ private NotificationCache findOrCreateNotificationCache(int reportID) { NotificationCache notificationCache = cache.get(reportID); @@ -135,6 +152,11 @@ private NotificationCache findOrCreateNotificationCache(int reportID) { return notificationCache; } + /** + * 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(); @@ -190,7 +212,7 @@ private IconCompat fetchIcon(@NonNull String urlString, @DrawableRes int fallbac private static class NotificationCache { public Map people = new HashMap<>(); public ArrayList messages = new ArrayList<>(); - public int prevNotificationId = -1; + public int prevNotificationID = -1; public static class Message { public Person person; From 7534047346bd62d4f64b72aae12f984796c4ab79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Mon, 2 Aug 2021 11:01:52 +0200 Subject: [PATCH 05/16] Use room name as the conversation title --- .../com/expensify/chat/CustomNotificationProvider.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java index 35b56b629dd9..1c14180d6339 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java @@ -103,6 +103,11 @@ private void applyMessageStyle(NotificationCompat.Builder builder, JsonMap paylo String accountID = Integer.toString(reportAction.get("actorAccountID").getInt(-1)); String message = reportAction.get("message").getList().get(0).getMap().get("text").getString(); long time = reportAction.get("timestamp").getLong(0); + String roomName = payload.get("roomName") == null ? "" : payload.get("roomName").getString(""); + String conversationTitle = "Chat with " + name; + if (!roomName.isEmpty()) { + conversationTitle = "#" + roomName; + } IconCompat iconCompat = fetchIcon(avatar, FALLBACK_ICON_ID); Person person = new Person.Builder() @@ -118,8 +123,8 @@ private void applyMessageStyle(NotificationCompat.Builder builder, JsonMap paylo notificationCache.messages.add(new NotificationCache.Message(person, message, time)); NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(person) - .setGroupConversation(notificationCache.people.size() > 2) - .setConversationTitle("Chat with " + name); + .setGroupConversation(notificationCache.people.size() > 2 || !roomName.isEmpty()) + .setConversationTitle(conversationTitle); for (NotificationCache.Message cachedMessage : notificationCache.messages) { messagingStyle.addMessage(cachedMessage.text, cachedMessage.time, cachedMessage.person); From 9aa6a0d738f4a7532dd62bd56baf1dcb92f12b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Mon, 9 Aug 2021 12:28:17 +0200 Subject: [PATCH 06/16] No newlines between imports --- .../java/com/expensify/chat/CustomNotificationProvider.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java index 1c14180d6339..da8257dbab78 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java @@ -7,14 +7,12 @@ import android.util.Log; import android.util.TypedValue; import android.view.WindowManager; - import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.app.Person; import androidx.core.graphics.drawable.IconCompat; - import com.urbanairship.AirshipConfigOptions; import com.urbanairship.json.JsonMap; import com.urbanairship.json.JsonValue; @@ -22,7 +20,6 @@ import com.urbanairship.push.notifications.NotificationArguments; import com.urbanairship.reactnative.ReactNotificationProvider; import com.urbanairship.util.ImageUtils; - import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; From f8bbdb6e11093d49af685a6f0ed91555cb7428c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Mon, 9 Aug 2021 13:02:44 +0200 Subject: [PATCH 07/16] No newlines between imports --- .../src/main/java/com/expensify/chat/CustomAirshipExtender.java | 2 -- .../java/com/expensify/chat/CustomNotificationListener.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java b/android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java index 32fa546102cd..dd09a934fc79 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java +++ b/android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java @@ -1,9 +1,7 @@ package com.expensify.chat; import android.content.Context; - import androidx.annotation.NonNull; - import com.urbanairship.UAirship; import com.urbanairship.push.NotificationListener; import com.urbanairship.push.PushManager; diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java index 73d8a1031654..d6fc1f9e1a35 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java @@ -1,12 +1,10 @@ package com.expensify.chat; import androidx.annotation.NonNull; - import com.urbanairship.push.NotificationActionButtonInfo; import com.urbanairship.push.NotificationInfo; import com.urbanairship.push.NotificationListener; import com.urbanairship.push.PushMessage; - import org.jetbrains.annotations.NotNull; /** From 640b3be9cdc0de4f8de9d4b429770fea17bb118f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Mon, 9 Aug 2021 13:07:54 +0200 Subject: [PATCH 08/16] Remove check for Date: Mon, 9 Aug 2021 13:16:15 +0200 Subject: [PATCH 09/16] Early return if reportID == -1 --- .../java/com/expensify/chat/CustomNotificationProvider.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java index d52d402b49e5..76ad327d554a 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java @@ -88,6 +88,10 @@ protected NotificationCompat.Builder onExtendBuilder(@NonNull Context context, @ */ private void applyMessageStyle(NotificationCompat.Builder builder, JsonMap payload, int notificationID) { int reportID = payload.get("reportID").getInt(-1); + if (reportID == -1) { + return; + } + NotificationCache notificationCache = findOrCreateNotificationCache(reportID); JsonMap reportAction = payload.get("reportAction").getMap(); String name = reportAction.get("person").getList().get(0).getMap().get("text").getString(); From 474203d6ca7dcf65cfb58d9fc4e30b9aa6a1b909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Mon, 9 Aug 2021 13:22:55 +0200 Subject: [PATCH 10/16] Retrieve person from cache instead of creating a new one every time. --- .../com/expensify/chat/CustomNotificationProvider.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java index 76ad327d554a..99a3e14c5af5 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java @@ -105,14 +105,15 @@ private void applyMessageStyle(NotificationCompat.Builder builder, JsonMap paylo conversationTitle = "#" + roomName; } - IconCompat iconCompat = fetchIcon(avatar, FALLBACK_ICON_ID); - Person person = new Person.Builder() + Person person = notificationCache.people.get(accountID); + if (person == null) { + IconCompat iconCompat = fetchIcon(avatar, FALLBACK_ICON_ID); + person = new Person.Builder() .setIcon(iconCompat) .setKey(accountID) .setName(name) .build(); - if (!notificationCache.people.containsKey(accountID)) { notificationCache.people.put(accountID, person); } From a68b7441ac9a90806177ffc43d5a83cb8edaaa8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Mon, 9 Aug 2021 16:24:45 +0200 Subject: [PATCH 11/16] Improve flow comments --- .../expensify/chat/CustomNotificationProvider.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java index 99a3e14c5af5..36200c3c4c1c 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java @@ -105,6 +105,7 @@ private void applyMessageStyle(NotificationCompat.Builder builder, JsonMap paylo conversationTitle = "#" + roomName; } + // Retrieve or create the Person object who sent the latest report comment Person person = notificationCache.people.get(accountID); if (person == null) { IconCompat iconCompat = fetchIcon(avatar, FALLBACK_ICON_ID); @@ -117,21 +118,34 @@ private void applyMessageStyle(NotificationCompat.Builder builder, JsonMap paylo notificationCache.people.put(accountID, person); } + // Store the latest report comment in the local conversation history notificationCache.messages.add(new NotificationCache.Message(person, message, time)); + // Create the messaging style notification builder for this notification. + // Associate the notification with the person who sent the report comment. + // If this conversation has 2 participants or more and there's no room name, we should mark + // it as a group conversation. + // Also set the conversation title. NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(person) .setGroupConversation(notificationCache.people.size() > 2 || !roomName.isEmpty()) .setConversationTitle(conversationTitle); + // Add all conversation messages to the notification, including the last one we just received. for (NotificationCache.Message cachedMessage : notificationCache.messages) { messagingStyle.addMessage(cachedMessage.text, cachedMessage.time, cachedMessage.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 (notificationCache.prevNotificationID != -1) { NotificationManagerCompat.from(context).cancel(notificationCache.prevNotificationID); } + // Apply the messaging style to the notification builder builder.setStyle(messagingStyle); + + // Store the new notification ID so we can replace the notification if this conversation + // receives more messages notificationCache.prevNotificationID = notificationID; } From c105f86ae84cba52d95817cb8429aa92bfe7f3da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Mon, 9 Aug 2021 16:28:50 +0200 Subject: [PATCH 12/16] Log the Push notification SendID --- .../java/com/expensify/chat/CustomNotificationProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java index 36200c3c4c1c..78ded2c3c344 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java @@ -71,7 +71,7 @@ protected NotificationCompat.Builder onExtendBuilder(@NonNull Context context, @ applyMessageStyle(builder, payload, arguments.getNotificationId()); } } catch (Exception e) { - Log.e(TAG, "Failed to parse conversation", e); + Log.e(TAG, "Failed to parse conversation. SendID=" + message.getSendId(), e); } } From 48a52184dd234a66fa930e70bdb13f44474bac77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Mon, 9 Aug 2021 16:30:05 +0200 Subject: [PATCH 13/16] Improve Log.e message and error --- .../java/com/expensify/chat/CustomNotificationProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java index 78ded2c3c344..906914969a9b 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java @@ -184,7 +184,7 @@ public void onDismissNotification(PushMessage message) { cache.remove(reportID); } catch (Exception e) { - Log.e(TAG, "Failed to delete conversation cache"); + Log.e(TAG, "Failed to delete conversation cache. SendID=" + message.getSendId(), e); } } From 6b61b147a53dd4a17d6768541020a268a9c94e86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Mon, 9 Aug 2021 16:34:03 +0200 Subject: [PATCH 14/16] Remove FALLBACK_ICON_ID as we don't have a fallback icon --- .../expensify/chat/CustomNotificationProvider.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java index 906914969a9b..d32e2975944b 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java @@ -6,7 +6,6 @@ import android.util.Log; import android.util.TypedValue; import android.view.WindowManager; -import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; @@ -38,9 +37,6 @@ public class CustomNotificationProvider extends ReactNotificationProvider { // to ensure the notification builds. private static final int MAX_ICON_FETCH_WAIT_TIME_SECONDS = 8; - // Fallback drawable ID. 0 to not use a fallback ID. - private static final int FALLBACK_ICON_ID = 0; - // Logging private static final String TAG = "NotificationProvider"; @@ -108,7 +104,7 @@ private void applyMessageStyle(NotificationCompat.Builder builder, JsonMap paylo // Retrieve or create the Person object who sent the latest report comment Person person = notificationCache.people.get(accountID); if (person == null) { - IconCompat iconCompat = fetchIcon(avatar, FALLBACK_ICON_ID); + IconCompat iconCompat = fetchIcon(avatar); person = new Person.Builder() .setIcon(iconCompat) .setKey(accountID) @@ -188,8 +184,7 @@ public void onDismissNotification(PushMessage message) { } } - @NonNull - private IconCompat fetchIcon(@NonNull String urlString, @DrawableRes int fallbackId) { + private IconCompat fetchIcon(String urlString) { // TODO: Add disk LRU cache URL parsedUrl = null; @@ -222,7 +217,7 @@ private IconCompat fetchIcon(@NonNull String urlString, @DrawableRes int fallbac } } - return fallbackId == 0 ? null : IconCompat.createWithResource(context, fallbackId); + return null; } private static class NotificationCache { From fb98d879edc6cc7f261ac8ad35abb29caf9c5ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Mon, 9 Aug 2021 16:35:46 +0200 Subject: [PATCH 15/16] Return early instead of wrap everything in an if block --- .../chat/CustomNotificationProvider.java | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java index d32e2975944b..83ba99bd9705 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java @@ -194,27 +194,29 @@ private IconCompat fetchIcon(String urlString) { Log.e(TAG, "Failed to resolve URL " + urlString, e); } - if (parsedUrl != null) { - WindowManager window = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - DisplayMetrics dm = new DisplayMetrics(); - window.getDefaultDisplay().getMetrics(dm); + if (parsedUrl == null) { + return null; + } - final int reqWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, MAX_ICON_SIZE_DPS, dm); - final int reqHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, MAX_ICON_SIZE_DPS, dm); + WindowManager window = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics dm = new DisplayMetrics(); + window.getDefaultDisplay().getMetrics(dm); - final URL url = parsedUrl; - Future future = executorService.submit(() -> ImageUtils.fetchScaledBitmap(context, url, reqWidth, reqHeight)); + final int reqWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, MAX_ICON_SIZE_DPS, dm); + final int reqHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, MAX_ICON_SIZE_DPS, dm); - try { - Bitmap bitmap = future.get(MAX_ICON_FETCH_WAIT_TIME_SECONDS, TimeUnit.SECONDS); - return IconCompat.createWithBitmap(bitmap); - } catch (InterruptedException e) { - Log.e(TAG,"Failed to fetch icon", e); - Thread.currentThread().interrupt(); - } catch (Exception e) { - Log.e(TAG,"Failed to fetch icon", e); - future.cancel(true); - } + final URL url = parsedUrl; + Future future = executorService.submit(() -> ImageUtils.fetchScaledBitmap(context, url, reqWidth, reqHeight)); + + try { + Bitmap bitmap = future.get(MAX_ICON_FETCH_WAIT_TIME_SECONDS, TimeUnit.SECONDS); + return IconCompat.createWithBitmap(bitmap); + } catch (InterruptedException e) { + Log.e(TAG,"Failed to fetch icon", e); + Thread.currentThread().interrupt(); + } catch (Exception e) { + Log.e(TAG,"Failed to fetch icon", e); + future.cancel(true); } return null; From 0c938c247accf95f36d3be0a620e3c1dbad99f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horus=20Lugo=20L=C3=B3pez?= Date: Wed, 11 Aug 2021 10:20:32 +0200 Subject: [PATCH 16/16] Remove TODO --- .../java/com/expensify/chat/CustomNotificationProvider.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java index 83ba99bd9705..d3efa5bc6196 100644 --- a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java +++ b/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java @@ -185,8 +185,6 @@ public void onDismissNotification(PushMessage message) { } private IconCompat fetchIcon(String urlString) { - // TODO: Add disk LRU cache - URL parsedUrl = null; try { parsedUrl = urlString == null ? null : new URL(urlString);