Skip to content

Commit 557864a

Browse files
committed
Reuse icons of notifications instead of building them for each message
1 parent fe9401e commit 557864a

9 files changed

Lines changed: 280 additions & 52 deletions

File tree

‎app/src/main/java/org/thunderdog/challegram/BaseApplication.kt‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import kotlinx.coroutines.CoroutineScope
2121
import kotlinx.coroutines.MainScope
2222
import org.thunderdog.challegram.push.FirebaseDeviceTokenRetriever
2323
import org.thunderdog.challegram.service.PushHandler
24+
import org.thunderdog.challegram.telegram.TdlibManager
2425
import org.thunderdog.challegram.unsorted.AppContext
2526
import org.thunderdog.challegram.unsorted.DeviceTokenRetrieverInstance
2627
import org.thunderdog.challegram.unsorted.Settings
@@ -69,6 +70,11 @@ class BaseApplication : TgxApplication(), Configuration.Provider {
6970
}
7071
}
7172

73+
override fun onTrimMemory(level: Int) {
74+
super.onTrimMemory(level)
75+
TdlibManager.trimMemory(level)
76+
}
77+
7278
override val workManagerConfiguration: Configuration
7379
get() = Configuration.Builder().build()
7480
}

‎app/src/main/java/org/thunderdog/challegram/config/Config.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ public static boolean useBundledWebp () {
9292

9393
public static final boolean NOTIFICATION_AUTO_CANCEL = true;
9494
public static final boolean NOTIFICATION_AUTO_CANCEL_SPECIFIC = true;
95+
public static final boolean REUSE_NOTIFICATION_ICONS = true;
9596
public static final int MAX_RUNNING_TDLIBS = 5;
9697

9798
public static final boolean NEED_LANGUAGE_WORKAROUND = false;

‎app/src/main/java/org/thunderdog/challegram/service/TGCallService.java‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -846,7 +846,9 @@ private void showNotification () {
846846
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
847847
builder.setColor(tdlib.accountColor());
848848
}
849-
Bitmap bitmap = TdlibNotificationUtils.buildLargeIcon(tdlib, user.profilePhoto != null ? user.profilePhoto.small : null, tdlib.cache().userAccentColor(user), TD.getLetters(user), false, true);
849+
Bitmap bitmap = TdlibNotificationUtils.buildLargeIcon(tdlib, ChatId.fromUserId(user.id),
850+
user.profilePhoto != null ? user.profilePhoto.small : null,
851+
tdlib.cache().userAccentColor(user), TD.getLetters(user), false, true);
850852
if (bitmap != null) {
851853
builder.setLargeIcon(bitmap);
852854
}
@@ -972,7 +974,10 @@ private boolean showIncomingNotification () {
972974
builder.setCategory(Notification.CATEGORY_CALL);
973975
builder.setFullScreenIntent(PendingIntent.getActivity(this, PendingIntent.FLAG_ONE_SHOT, Intents.valueOfCall(), Intents.mutabilityFlags(false)), true);
974976
}
975-
Bitmap bitmap = user != null ? TdlibNotificationUtils.buildLargeIcon(tdlib, user.profilePhoto != null ? user.profilePhoto.small : null, tdlib.cache().userAccentColor(user), TD.getLetters(user), false, true) : null;
977+
Bitmap bitmap = user != null ?
978+
TdlibNotificationUtils.buildLargeIcon(tdlib, ChatId.fromUserId(user.id),
979+
user.profilePhoto != null ? user.profilePhoto.small : null,
980+
tdlib.cache().userAccentColor(user), TD.getLetters(user), false, true) : null;
976981
if (bitmap != null) {
977982
builder.setLargeIcon(bitmap);
978983
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package org.thunderdog.challegram.telegram
2+
3+
import android.content.ComponentCallbacks2
4+
import android.graphics.Bitmap
5+
import android.util.LruCache
6+
import org.thunderdog.challegram.theme.ColorState
7+
import org.thunderdog.challegram.theme.ThemeChangeListener
8+
import org.thunderdog.challegram.theme.ThemeDelegate
9+
import org.thunderdog.challegram.theme.ThemeManager
10+
import org.thunderdog.challegram.tool.UI
11+
12+
/** One cache for all accounts: an icon stays while a shown notification uses it */
13+
class NotificationIcons(maxSize: Int) : ThemeChangeListener {
14+
private val icons = object : LruCache<String, Bitmap>(maxSize) {
15+
override fun sizeOf(key: String, value: Bitmap): Int = value.byteCount
16+
}
17+
18+
private val usedIcons = HashMap<String, MutableSet<String>>()
19+
private var isListeningTheme = false
20+
21+
private fun ownerOf(accountId: Int, chatId: Long): String = "$accountId:$chatId"
22+
23+
@Synchronized
24+
fun get(accountId: Int, chatId: Long, key: String): Bitmap? {
25+
val icon = icons.get("$accountId:$key") ?: return null
26+
usedIcons.getOrPut(ownerOf(accountId, chatId)) { HashSet() }.add(key)
27+
return icon
28+
}
29+
30+
@Synchronized
31+
fun put(accountId: Int, chatId: Long, key: String, icon: Bitmap): Bitmap {
32+
if (!isListeningTheme) {
33+
isListeningTheme = true
34+
UI.post { ThemeManager.instance().addThemeListener(this) }
35+
}
36+
val copy = icon.copy(Bitmap.Config.ARGB_8888, false) ?: return icon
37+
icon.recycle()
38+
icons.put("$accountId:$key", copy)
39+
usedIcons.getOrPut(ownerOf(accountId, chatId)) { HashSet() }.add(key)
40+
return copy
41+
}
42+
43+
@Synchronized
44+
fun resetUsedIcons(accountId: Int, chatId: Long) {
45+
usedIcons.remove(ownerOf(accountId, chatId))
46+
}
47+
48+
@Synchronized
49+
fun retainChats(accountId: Int, chatIds: Set<Long>) {
50+
val prefix = "$accountId:"
51+
usedIcons.keys.removeAll {
52+
it.startsWith(prefix) && it.substring(prefix.length).toLong() !in chatIds
53+
}
54+
val used = HashSet<String>()
55+
for ((owner, keys) in usedIcons) {
56+
if (owner.startsWith(prefix)) {
57+
used.addAll(keys)
58+
}
59+
}
60+
for (key in icons.snapshot().keys) {
61+
if (key.startsWith(prefix) && key.substring(prefix.length) !in used) {
62+
icons.remove(key)
63+
}
64+
}
65+
}
66+
67+
fun releaseAccount(accountId: Int) {
68+
retainChats(accountId, emptySet())
69+
}
70+
71+
fun clear() {
72+
icons.evictAll()
73+
}
74+
75+
fun trimMemory(level: Int) {
76+
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND ||
77+
level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW ||
78+
level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
79+
) {
80+
clear()
81+
}
82+
}
83+
84+
override fun needsTempUpdates(): Boolean = false
85+
86+
override fun onThemeColorsChanged(areTemp: Boolean, state: ColorState?) {
87+
if (!areTemp) {
88+
clear()
89+
}
90+
}
91+
92+
override fun onThemeChanged(fromTheme: ThemeDelegate, toTheme: ThemeDelegate) {
93+
clear()
94+
}
95+
}

‎app/src/main/java/org/thunderdog/challegram/telegram/TdlibManager.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@ public void handleMessage (Message msg) {
311311
private final ManagerHandler handler = new ManagerHandler(this);
312312
private final LiveLocationManager liveLocationManager = new LiveLocationManager(this);
313313
private final TdlibNotificationManager.NotificationQueue notificationQueue = new TdlibNotificationManager.NotificationQueue("NotificationQueue", this);
314+
private final NotificationIcons notificationIcons =
315+
Config.REUSE_NOTIFICATION_ICONS ? new NotificationIcons(4 * 1024 * 1024) : null;
314316
private final CallManager calls = new CallManager(this);
315317
private final Settings.ProxyChangeListener proxyChangeListener = new Settings.ProxyChangeListener() {
316318
@Override
@@ -507,6 +509,17 @@ TdlibNotificationManager.NotificationQueue notificationQueue () {
507509
return notificationQueue;
508510
}
509511

512+
public NotificationIcons notificationIcons () {
513+
return notificationIcons;
514+
}
515+
516+
public static void trimMemory (int level) {
517+
final TdlibManager manager = instance;
518+
if (manager != null && manager.notificationIcons != null) {
519+
manager.notificationIcons.trimMemory(level);
520+
}
521+
}
522+
510523
private void load (int specificAccountId, boolean forceService) {
511524
readAccountConfig();
512525
TdlibAccount selectedAccount = specificAccountId != TdlibAccount.NO_ID ? accounts.get(specificAccountId) : currentAccount;

‎app/src/main/java/org/thunderdog/challegram/telegram/TdlibNotificationHelper.java‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ public void restoreState (TdApi.UpdateActiveNotifications update) {
165165
rebuild();
166166
}
167167
}
168+
retainIcons();
168169
}
169170

170171
private boolean haveRebuilt;
@@ -478,6 +479,7 @@ public void onHideAll (int category) {
478479
if (group.matchesCategory(category))
479480
group.markAsHidden(TdlibNotificationGroup.HIDE_REASON_GLOBAL);
480481
}
482+
retainIcons();
481483
}
482484

483485
public void onHide (TdlibNotificationExtras extras) {
@@ -487,6 +489,7 @@ public void onHide (TdlibNotificationExtras extras) {
487489
} else {
488490
tdlib.settings().setNotificationGroupData(extras.notificationGroupId, extras.maxNotificationId, 0);
489491
}
492+
retainIcons();
490493
}
491494

492495
public void onDropNotificationData (boolean hideAll) {
@@ -508,6 +511,7 @@ public void onDropNotificationData (boolean hideAll) {
508511
if (!hideAll && update) {
509512
rebuild();
510513
}
514+
retainIcons();
511515
}
512516

513517
public long findSingleChatId () {
@@ -558,6 +562,7 @@ private void displayNotificationGroup (@NonNull TdlibNotificationGroup group, bo
558562
TdlibNotificationSettings settings = needNotification && !group.isHidden() ? new TdlibNotificationSettings(tdlib, notificationSettingsChatId, group) : null;
559563
style.displayNotificationGroup(context, this, badgeCount, allowPreview, group, settings);
560564
tdlib.context().setHavePendingNotifications(tdlib.id(), true);
565+
retainIcons();
561566
}
562567

563568
private void hideNotificationGroup (@NonNull TdlibNotificationGroup group) {
@@ -566,6 +571,7 @@ private void hideNotificationGroup (@NonNull TdlibNotificationGroup group) {
566571
boolean allowPreview = allowNotificationPreview();
567572
style.hideNotificationGroup(context, this, badgeCount, allowPreview, group);
568573
tdlib.context().setHavePendingNotifications(tdlib.id(), !isEmpty());
574+
retainIcons();
569575
}
570576

571577
private void rebuild (@Nullable TdApi.NotificationSettingsScope scope, long specificChatId, int specificGroupId) {
@@ -577,6 +583,7 @@ private void rebuild (@Nullable TdApi.NotificationSettingsScope scope, long spec
577583
style.rebuildNotificationsSilently(context, this, badgeCount, allowPreview, scope, specificChatId, specificGroupId);
578584
}
579585
tdlib.context().setHavePendingNotifications(tdlib.id(), haveNotifications);
586+
retainIcons();
580587
}
581588

582589
public void rebuild () {
@@ -595,6 +602,28 @@ public void rebuildGroup (int groupId) {
595602
rebuild(null, 0, groupId);
596603
}
597604

605+
private void retainIcons () {
606+
// Muted groups keep their icons while mentions or pinned messages can still come there
607+
if (!Config.REUSE_NOTIFICATION_ICONS) {
608+
return;
609+
}
610+
final Set<Long> chatIds = new HashSet<>();
611+
for (TdlibNotificationGroup group : this) {
612+
final long chatId = group.getChatId();
613+
if (canNotify(chatId)) {
614+
chatIds.add(chatId);
615+
}
616+
}
617+
tdlib.context().notificationIcons().retainChats(tdlib.id(), chatIds);
618+
}
619+
620+
private boolean canNotify (long chatId) {
621+
final TdlibNotificationManager notifications = tdlib.notifications();
622+
return tdlib.chatNotificationsEnabled(chatId) || (tdlib.isMultiChat(chatId) && !(
623+
notifications.areMentionsDisabled(chatId) && notifications.arePinnedMessagesDisabled(chatId)
624+
));
625+
}
626+
598627
public void onChatOpened (long chatId) {
599628
if (ChatId.isSecret(chatId)) {
600629
for (TdlibNotificationGroup group : groups.values()) {

‎app/src/main/java/org/thunderdog/challegram/telegram/TdlibNotificationManager.java‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2027,6 +2027,9 @@ public void onPerformStartup (boolean isAfterRestart) {
20272027
@Override
20282028
public void onPerformUserCleanup () {
20292029
resetNotificationSettings(true);
2030+
if (Config.REUSE_NOTIFICATION_ICONS) {
2031+
tdlib.context().notificationIcons().releaseAccount(tdlib.id());
2032+
}
20302033
}
20312034

20322035
@Override
@@ -2155,7 +2158,20 @@ void onUpdateNotificationSettings (TdApi.UpdateChatNotificationSettings update,
21552158
boolean defaultShowPreview = defaultShowPreview(chatId);
21562159
boolean hadPreview = oldNotificationSettings.useDefaultShowPreview ? defaultShowPreview : oldNotificationSettings.showPreview;
21572160
boolean hasPreview = update.notificationSettings.useDefaultShowPreview ? defaultShowPreview : update.notificationSettings.showPreview;
2158-
if (hadPreview != hasPreview) {
2161+
// Icons of the chat leave when it can't notify anymore, see TdlibNotificationHelper.retainIcons
2162+
final TdApi.ChatNotificationSettings settings = update.notificationSettings;
2163+
final boolean isSwitched = Config.REUSE_NOTIFICATION_ICONS && (
2164+
settings.useDefaultMuteFor != oldNotificationSettings.useDefaultMuteFor ||
2165+
(settings.muteFor == 0) != (oldNotificationSettings.muteFor == 0) ||
2166+
settings.useDefaultDisableMentionNotifications !=
2167+
oldNotificationSettings.useDefaultDisableMentionNotifications ||
2168+
settings.disableMentionNotifications != oldNotificationSettings.disableMentionNotifications ||
2169+
settings.useDefaultDisablePinnedMessageNotifications !=
2170+
oldNotificationSettings.useDefaultDisablePinnedMessageNotifications ||
2171+
settings.disablePinnedMessageNotifications !=
2172+
oldNotificationSettings.disablePinnedMessageNotifications
2173+
);
2174+
if (hadPreview != hasPreview || isSwitched) {
21592175
onUpdateNotifications(update.chatId);
21602176
}
21612177
}
@@ -2166,6 +2182,12 @@ void onUpdateNotificationSettings (TdApi.UpdateScopeNotificationSettings update)
21662182
TdApi.ScopeNotificationSettings settings = update.notificationSettings;
21672183
TdApi.ScopeNotificationSettings existingSettings = getScopeNotificationSettings(scope);
21682184
boolean previewToggled = existingSettings != null && existingSettings.showPreview != settings.showPreview;
2185+
final boolean isSwitched = Config.REUSE_NOTIFICATION_ICONS && existingSettings != null && (
2186+
(existingSettings.muteFor == 0) != (settings.muteFor == 0) ||
2187+
existingSettings.disableMentionNotifications != settings.disableMentionNotifications ||
2188+
existingSettings.disablePinnedMessageNotifications !=
2189+
settings.disablePinnedMessageNotifications
2190+
);
21692191
switch (scope.getConstructor()) {
21702192
case TdApi.NotificationSettingsScopePrivateChats.CONSTRUCTOR:
21712193
settingsForPrivateChats = settings;
@@ -2179,7 +2201,7 @@ void onUpdateNotificationSettings (TdApi.UpdateScopeNotificationSettings update)
21792201
default:
21802202
throw new RuntimeException();
21812203
}
2182-
if (previewToggled) {
2204+
if (previewToggled || isSwitched) {
21832205
onUpdateNotifications(scope);
21842206
}
21852207
}

0 commit comments

Comments
 (0)