/* * This is the source code of Telegram for Android v. 5.x.x. * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * * Copyright Nikolai Kudashov, 2013-2018. */ package org.telegram.messenger; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ShortcutManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.SpannedString; import android.text.TextUtils; import android.text.style.CharacterStyle; import android.util.LongSparseArray; import android.util.SparseArray; import org.telegram.SQLite.SQLiteCursor; import org.telegram.SQLite.SQLiteDatabase; import org.telegram.SQLite.SQLitePreparedStatement; import org.telegram.messenger.support.SparseLongArray; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.NativeByteBuffer; import org.telegram.tgnet.SerializedData; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.Bulletin; import org.telegram.ui.Components.StickerSetBulletinLayout; import org.telegram.ui.Components.StickersArchiveAlert; import org.telegram.ui.Components.TextStyleSpan; import org.telegram.ui.Components.URLSpanReplacement; import org.telegram.ui.Components.URLSpanUserMention; import org.telegram.ui.LaunchActivity; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import androidx.core.content.pm.ShortcutInfoCompat; import androidx.core.content.pm.ShortcutManagerCompat; import androidx.core.graphics.drawable.IconCompat; @SuppressWarnings("unchecked") public class MediaDataController extends BaseController { public static String SHORTCUT_CATEGORY = "org.telegram.messenger.SHORTCUT_SHARE"; private static volatile MediaDataController[] Instance = new MediaDataController[UserConfig.MAX_ACCOUNT_COUNT]; public static MediaDataController getInstance(int num) { MediaDataController localInstance = Instance[num]; if (localInstance == null) { synchronized (MediaDataController.class) { localInstance = Instance[num]; if (localInstance == null) { Instance[num] = localInstance = new MediaDataController(num); } } } return localInstance; } public MediaDataController(int num) { super(num); if (currentAccount == 0) { draftPreferences = ApplicationLoader.applicationContext.getSharedPreferences("drafts", Activity.MODE_PRIVATE); } else { draftPreferences = ApplicationLoader.applicationContext.getSharedPreferences("drafts" + currentAccount, Activity.MODE_PRIVATE); } Map values = draftPreferences.getAll(); for (Map.Entry entry : values.entrySet()) { try { String key = entry.getKey(); long did = Utilities.parseLong(key); byte[] bytes = Utilities.hexToBytes((String) entry.getValue()); SerializedData serializedData = new SerializedData(bytes); boolean isThread = false; if (key.startsWith("r_") || (isThread = key.startsWith("rt_"))) { TLRPC.Message message = TLRPC.Message.TLdeserialize(serializedData, serializedData.readInt32(true), true); if (message != null) { message.readAttachPath(serializedData, getUserConfig().clientUserId); SparseArray threads = draftMessages.get(did); if (threads == null) { threads = new SparseArray<>(); draftMessages.put(did, threads); } int threadId = isThread ? Utilities.parseInt(key.substring(key.lastIndexOf('_') + 1)) : 0; threads.put(threadId, message); } } else { TLRPC.DraftMessage draftMessage = TLRPC.DraftMessage.TLdeserialize(serializedData, serializedData.readInt32(true), true); if (draftMessage != null) { SparseArray threads = drafts.get(did); if (threads == null) { threads = new SparseArray<>(); drafts.put(did, threads); } int threadId = key.startsWith("t_") ? Utilities.parseInt(key.substring(key.lastIndexOf('_') + 1)) : 0; threads.put(threadId, draftMessage); } } serializedData.cleanup(); } catch (Exception e) { //igonre } } } public static final int TYPE_IMAGE = 0; public static final int TYPE_MASK = 1; public static final int TYPE_FAVE = 2; public static final int TYPE_FEATURED = 3; public static final int TYPE_EMOJI = 4; public static final int TYPE_GREETINGS = 3; private ArrayList[] stickerSets = new ArrayList[]{new ArrayList<>(), new ArrayList<>(), new ArrayList<>(0), new ArrayList<>(), new ArrayList<>()}; private LongSparseArray[] stickersByIds = new LongSparseArray[]{new LongSparseArray<>(), new LongSparseArray<>(), new LongSparseArray<>(), new LongSparseArray<>(), new LongSparseArray<>()}; private LongSparseArray stickerSetsById = new LongSparseArray<>(); private LongSparseArray installedStickerSetsById = new LongSparseArray<>(); private LongSparseArray groupStickerSets = new LongSparseArray<>(); private ConcurrentHashMap stickerSetsByName = new ConcurrentHashMap<>(100, 1.0f, 1); private HashMap diceStickerSetsByEmoji = new HashMap<>(); private LongSparseArray diceEmojiStickerSetsById = new LongSparseArray<>(); private HashSet loadingDiceStickerSets = new HashSet<>(); private LongSparseArray removingStickerSetsUndos = new LongSparseArray<>(); private Runnable[] scheduledLoadStickers = new Runnable[5]; private boolean[] loadingStickers = new boolean[5]; private boolean[] stickersLoaded = new boolean[5]; private int[] loadHash = new int[5]; private int[] loadDate = new int[5]; private HashMap> verifyingMessages = new HashMap<>(); private int[] archivedStickersCount = new int[2]; private LongSparseArray stickersByEmoji = new LongSparseArray<>(); private HashMap> allStickers = new HashMap<>(); private HashMap> allStickersFeatured = new HashMap<>(); private ArrayList[] recentStickers = new ArrayList[]{new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()}; private boolean[] loadingRecentStickers = new boolean[4]; private boolean[] recentStickersLoaded = new boolean[4]; private ArrayList recentGifs = new ArrayList<>(); private boolean loadingRecentGifs; private boolean recentGifsLoaded; private int loadFeaturedHash; private int loadFeaturedDate; private ArrayList featuredStickerSets = new ArrayList<>(); private LongSparseArray featuredStickerSetsById = new LongSparseArray<>(); private ArrayList unreadStickerSets = new ArrayList<>(); private ArrayList readingStickerSets = new ArrayList<>(); private boolean loadingFeaturedStickers; private boolean featuredStickersLoaded; private TLRPC.Document greetingsSticker; public void cleanup() { for (int a = 0; a < recentStickers.length; a++) { recentStickers[a].clear(); loadingRecentStickers[a] = false; recentStickersLoaded[a] = false; } for (int a = 0; a < 4; a++) { loadHash[a] = 0; loadDate[a] = 0; stickerSets[a].clear(); loadingStickers[a] = false; stickersLoaded[a] = false; } featuredStickerSets.clear(); loadingPinnedMessages.clear(); loadFeaturedDate = 0; loadFeaturedHash = 0; allStickers.clear(); allStickersFeatured.clear(); stickersByEmoji.clear(); featuredStickerSetsById.clear(); featuredStickerSets.clear(); unreadStickerSets.clear(); recentGifs.clear(); stickerSetsById.clear(); installedStickerSetsById.clear(); stickerSetsByName.clear(); diceStickerSetsByEmoji.clear(); diceEmojiStickerSetsById.clear(); loadingDiceStickerSets.clear(); loadingFeaturedStickers = false; featuredStickersLoaded = false; loadingRecentGifs = false; recentGifsLoaded = false; currentFetchingEmoji.clear(); if (Build.VERSION.SDK_INT >= 25) { Utilities.globalQueue.postRunnable(() -> { try { ShortcutManagerCompat.removeAllDynamicShortcuts(ApplicationLoader.applicationContext); } catch (Exception e) { FileLog.e(e); } }); } verifyingMessages.clear(); loading = false; loaded = false; hints.clear(); inlineBots.clear(); getNotificationCenter().postNotificationName(NotificationCenter.reloadHints); getNotificationCenter().postNotificationName(NotificationCenter.reloadInlineHints); drafts.clear(); draftMessages.clear(); draftPreferences.edit().clear().commit(); botInfos.clear(); botKeyboards.clear(); botKeyboardsByMids.clear(); } public void checkStickers(int type) { if (!loadingStickers[type] && (!stickersLoaded[type] || Math.abs(System.currentTimeMillis() / 1000 - loadDate[type]) >= 60 * 60)) { loadStickers(type, true, false); } } public void checkFeaturedStickers() { if (!loadingFeaturedStickers && (!featuredStickersLoaded || Math.abs(System.currentTimeMillis() / 1000 - loadFeaturedDate) >= 60 * 60)) { loadFeaturedStickers(true, false); } } public ArrayList getRecentStickers(int type) { ArrayList arrayList = recentStickers[type]; return new ArrayList<>(arrayList.subList(0, Math.min(arrayList.size(), 20))); } public ArrayList getRecentStickersNoCopy(int type) { return recentStickers[type]; } public boolean isStickerInFavorites(TLRPC.Document document) { if (document == null) { return false; } for (int a = 0; a < recentStickers[TYPE_FAVE].size(); a++) { TLRPC.Document d = recentStickers[TYPE_FAVE].get(a); if (d.id == document.id && d.dc_id == document.dc_id) { return true; } } return false; } public void addRecentSticker(final int type, Object parentObject, TLRPC.Document document, int date, boolean remove) { if (type == TYPE_GREETINGS || !MessageObject.isStickerDocument(document) && !MessageObject.isAnimatedStickerDocument(document, true)) { return; } boolean found = false; for (int a = 0; a < recentStickers[type].size(); a++) { TLRPC.Document image = recentStickers[type].get(a); if (image.id == document.id) { recentStickers[type].remove(a); if (!remove) { recentStickers[type].add(0, image); } found = true; break; } } if (!found && !remove) { recentStickers[type].add(0, document); } int maxCount; if (type == TYPE_FAVE) { if (remove) { NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.showBulletin, Bulletin.TYPE_STICKER, document, StickerSetBulletinLayout.TYPE_REMOVED_FROM_FAVORITES); } else { NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.showBulletin, Bulletin.TYPE_STICKER, document, StickerSetBulletinLayout.TYPE_ADDED_TO_FAVORITES); } TLRPC.TL_messages_faveSticker req = new TLRPC.TL_messages_faveSticker(); req.id = new TLRPC.TL_inputDocument(); req.id.id = document.id; req.id.access_hash = document.access_hash; req.id.file_reference = document.file_reference; if (req.id.file_reference == null) { req.id.file_reference = new byte[0]; } req.unfave = remove; getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null && FileRefController.isFileRefError(error.text) && parentObject != null) { getFileRefController().requestReference(parentObject, req); } else { AndroidUtilities.runOnUIThread(() -> getMediaDataController().loadRecents(MediaDataController.TYPE_FAVE, false, false, true)); } }); maxCount = getMessagesController().maxFaveStickersCount; } else { if (type == TYPE_IMAGE && remove) { NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.showBulletin, Bulletin.TYPE_STICKER, document, StickerSetBulletinLayout.TYPE_REMOVED_FROM_RECENT); TLRPC.TL_messages_saveRecentSticker req = new TLRPC.TL_messages_saveRecentSticker(); req.id = new TLRPC.TL_inputDocument(); req.id.id = document.id; req.id.access_hash = document.access_hash; req.id.file_reference = document.file_reference; if (req.id.file_reference == null) { req.id.file_reference = new byte[0]; } req.unsave = true; getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null && FileRefController.isFileRefError(error.text) && parentObject != null) { getFileRefController().requestReference(parentObject, req); } }); } maxCount = getMessagesController().maxRecentStickersCount; } if (recentStickers[type].size() > maxCount || remove) { final TLRPC.Document old = remove ? document : recentStickers[type].remove(recentStickers[type].size() - 1); getMessagesStorage().getStorageQueue().postRunnable(() -> { int cacheType; if (type == TYPE_IMAGE) { cacheType = 3; } else if (type == TYPE_MASK) { cacheType = 4; } else { cacheType = 5; } try { getMessagesStorage().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + old.id + "' AND type = " + cacheType).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } }); } if (!remove) { ArrayList arrayList = new ArrayList<>(); arrayList.add(document); processLoadedRecentDocuments(type, arrayList, false, date, false); } if (type == TYPE_FAVE || type == TYPE_IMAGE && remove) { getNotificationCenter().postNotificationName(NotificationCenter.recentDocumentsDidLoad, false, type); } } public ArrayList getRecentGifs() { return new ArrayList<>(recentGifs); } public void removeRecentGif(final TLRPC.Document document) { for (int i = 0, N = recentGifs.size(); i < N; i++) { if (recentGifs.get(i).id == document.id) { recentGifs.remove(i); break; } } TLRPC.TL_messages_saveGif req = new TLRPC.TL_messages_saveGif(); req.id = new TLRPC.TL_inputDocument(); req.id.id = document.id; req.id.access_hash = document.access_hash; req.id.file_reference = document.file_reference; if (req.id.file_reference == null) { req.id.file_reference = new byte[0]; } req.unsave = true; getConnectionsManager().sendRequest(req, (response, error) -> { if (error != null && FileRefController.isFileRefError(error.text)) { getFileRefController().requestReference("gif", req); } }); getMessagesStorage().getStorageQueue().postRunnable(() -> { try { getMessagesStorage().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + document.id + "' AND type = 2").stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } }); } public boolean hasRecentGif(TLRPC.Document document) { for (int a = 0; a < recentGifs.size(); a++) { TLRPC.Document image = recentGifs.get(a); if (image.id == document.id) { recentGifs.remove(a); recentGifs.add(0, image); return true; } } return false; } public void addRecentGif(TLRPC.Document document, int date) { if (document == null) { return; } boolean found = false; for (int a = 0; a < recentGifs.size(); a++) { TLRPC.Document image = recentGifs.get(a); if (image.id == document.id) { recentGifs.remove(a); recentGifs.add(0, image); found = true; break; } } if (!found) { recentGifs.add(0, document); } if (recentGifs.size() > getMessagesController().maxRecentGifsCount) { final TLRPC.Document old = recentGifs.remove(recentGifs.size() - 1); getMessagesStorage().getStorageQueue().postRunnable(() -> { try { getMessagesStorage().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + old.id + "' AND type = 2").stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } }); } ArrayList arrayList = new ArrayList<>(); arrayList.add(document); processLoadedRecentDocuments(0, arrayList, true, date, false); } public boolean isLoadingStickers(int type) { return loadingStickers[type]; } public void replaceStickerSet(TLRPC.TL_messages_stickerSet set) { TLRPC.TL_messages_stickerSet existingSet = stickerSetsById.get(set.set.id); String emoji = diceEmojiStickerSetsById.get(set.set.id); if (emoji != null) { diceStickerSetsByEmoji.put(emoji, set); putDiceStickersToCache(emoji, set, (int) (System.currentTimeMillis() / 1000)); } boolean isGroupSet = false; if (existingSet == null) { existingSet = stickerSetsByName.get(set.set.short_name); } if (existingSet == null) { existingSet = groupStickerSets.get(set.set.id); if (existingSet != null) { isGroupSet = true; } } if (existingSet == null) { return; } boolean changed = false; if ("AnimatedEmojies".equals(set.set.short_name)) { changed = true; existingSet.documents = set.documents; existingSet.packs = set.packs; existingSet.set = set.set; AndroidUtilities.runOnUIThread(() -> { LongSparseArray stickersById = getStickerByIds(TYPE_EMOJI); for (int b = 0; b < set.documents.size(); b++) { TLRPC.Document document = set.documents.get(b); stickersById.put(document.id, document); } }); } else { LongSparseArray documents = new LongSparseArray<>(); for (int a = 0, size = set.documents.size(); a < size; a++) { TLRPC.Document document = set.documents.get(a); documents.put(document.id, document); } for (int a = 0, size = existingSet.documents.size(); a < size; a++) { TLRPC.Document document = existingSet.documents.get(a); TLRPC.Document newDocument = documents.get(document.id); if (newDocument != null) { existingSet.documents.set(a, newDocument); changed = true; } } } if (changed) { if (isGroupSet) { putSetToCache(existingSet); } else { int type = set.set.masks ? TYPE_MASK : TYPE_IMAGE; putStickersToCache(type, stickerSets[type], loadDate[type], loadHash[type]); if ("AnimatedEmojies".equals(set.set.short_name)) { type = TYPE_EMOJI; putStickersToCache(type, stickerSets[type], loadDate[type], loadHash[type]); } } } } public TLRPC.TL_messages_stickerSet getStickerSetByName(String name) { return stickerSetsByName.get(name); } public TLRPC.TL_messages_stickerSet getStickerSetByEmojiOrName(String emoji) { return diceStickerSetsByEmoji.get(emoji); } public TLRPC.TL_messages_stickerSet getStickerSetById(long id) { return stickerSetsById.get(id); } public TLRPC.TL_messages_stickerSet getGroupStickerSetById(TLRPC.StickerSet stickerSet) { TLRPC.TL_messages_stickerSet set = stickerSetsById.get(stickerSet.id); if (set == null) { set = groupStickerSets.get(stickerSet.id); if (set == null || set.set == null) { loadGroupStickerSet(stickerSet, true); } else if (set.set.hash != stickerSet.hash) { loadGroupStickerSet(stickerSet, false); } } return set; } public void putGroupStickerSet(TLRPC.TL_messages_stickerSet stickerSet) { groupStickerSets.put(stickerSet.set.id, stickerSet); } private void loadGroupStickerSet(final TLRPC.StickerSet stickerSet, boolean cache) { if (cache) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { final TLRPC.TL_messages_stickerSet set; SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT document FROM web_recent_v3 WHERE id = 's_" + stickerSet.id + "'"); if (cursor.next() && !cursor.isNull(0)) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { set = TLRPC.TL_messages_stickerSet.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } else { set = null; } } else { set = null; } cursor.dispose(); if (set == null || set.set == null || set.set.hash != stickerSet.hash) { loadGroupStickerSet(stickerSet, false); } if (set != null && set.set != null) { AndroidUtilities.runOnUIThread(() -> { groupStickerSets.put(set.set.id, set); getNotificationCenter().postNotificationName(NotificationCenter.groupStickersDidLoad, set.set.id); }); } } catch (Throwable e) { FileLog.e(e); } }); } else { TLRPC.TL_messages_getStickerSet req = new TLRPC.TL_messages_getStickerSet(); req.stickerset = new TLRPC.TL_inputStickerSetID(); req.stickerset.id = stickerSet.id; req.stickerset.access_hash = stickerSet.access_hash; getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { TLRPC.TL_messages_stickerSet set = (TLRPC.TL_messages_stickerSet) response; AndroidUtilities.runOnUIThread(() -> { groupStickerSets.put(set.set.id, set); getNotificationCenter().postNotificationName(NotificationCenter.groupStickersDidLoad, set.set.id); }); } }); } } private void putSetToCache(TLRPC.TL_messages_stickerSet set) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { SQLiteDatabase database = getMessagesStorage().getDatabase(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO web_recent_v3 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); state.requery(); state.bindString(1, "s_" + set.set.id); state.bindInteger(2, 6); state.bindString(3, ""); state.bindString(4, ""); state.bindString(5, ""); state.bindInteger(6, 0); state.bindInteger(7, 0); state.bindInteger(8, 0); state.bindInteger(9, 0); NativeByteBuffer data = new NativeByteBuffer(set.getObjectSize()); set.serializeToStream(data); state.bindByteBuffer(10, data); state.step(); data.reuse(); state.dispose(); } catch (Exception e) { FileLog.e(e); } }); } public HashMap> getAllStickers() { return allStickers; } public HashMap> getAllStickersFeatured() { return allStickersFeatured; } public TLRPC.Document getEmojiAnimatedSticker(CharSequence message) { String emoji = message.toString().replace("\uFE0F", ""); ArrayList arrayList = getStickerSets(MediaDataController.TYPE_EMOJI); for (int a = 0, N = arrayList.size(); a < N; a++) { TLRPC.TL_messages_stickerSet set = arrayList.get(a); for (int b = 0, N2 = set.packs.size(); b < N2; b++) { TLRPC.TL_stickerPack pack = set.packs.get(b); if (!pack.documents.isEmpty() && TextUtils.equals(pack.emoticon, emoji)) { LongSparseArray stickerByIds = getStickerByIds(MediaDataController.TYPE_EMOJI); return stickerByIds.get(pack.documents.get(0)); } } } return null; } public boolean canAddStickerToFavorites() { return !stickersLoaded[0] || stickerSets[0].size() >= 5 || !recentStickers[TYPE_FAVE].isEmpty(); } public ArrayList getStickerSets(int type) { if (type == TYPE_FEATURED) { return stickerSets[2]; } else { return stickerSets[type]; } } public LongSparseArray getStickerByIds(int type) { return stickersByIds[type]; } public ArrayList getFeaturedStickerSets() { return featuredStickerSets; } public ArrayList getUnreadStickerSets() { return unreadStickerSets; } public boolean areAllTrendingStickerSetsUnread() { for (int a = 0, N = featuredStickerSets.size(); a < N; a++) { TLRPC.StickerSetCovered pack = featuredStickerSets.get(a); if (isStickerPackInstalled(pack.set.id) || pack.covers.isEmpty() && pack.cover == null) { continue; } if (!unreadStickerSets.contains(pack.set.id)) { return false; } } return true; } public boolean isStickerPackInstalled(long id) { return installedStickerSetsById.indexOfKey(id) >= 0; } public boolean isStickerPackUnread(long id) { return unreadStickerSets.contains(id); } public boolean isStickerPackInstalled(String name) { return stickerSetsByName.containsKey(name); } public String getEmojiForSticker(long id) { String value = stickersByEmoji.get(id); return value != null ? value : ""; } public static int calcDocumentsHash(ArrayList arrayList) { return calcDocumentsHash(arrayList, 200); } public static int calcDocumentsHash(ArrayList arrayList, int maxCount) { if (arrayList == null) { return 0; } long acc = 0; for (int a = 0, N = Math.min(maxCount, arrayList.size()); a < N; a++) { TLRPC.Document document = arrayList.get(a); if (document == null) { continue; } int high_id = (int) (document.id >> 32); int lower_id = (int) document.id; acc = ((acc * 20261) + 0x80000000L + high_id) % 0x80000000L; acc = ((acc * 20261) + 0x80000000L + lower_id) % 0x80000000L; } return (int) acc; } public void loadRecents(final int type, final boolean gif, boolean cache, boolean force) { if (gif) { if (loadingRecentGifs) { return; } loadingRecentGifs = true; if (recentGifsLoaded) { cache = false; } } else { if (loadingRecentStickers[type]) { return; } loadingRecentStickers[type] = true; if (recentStickersLoaded[type]) { cache = false; } } if (cache) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { final int cacheType; if (gif) { cacheType = 2; } else if (type == TYPE_IMAGE) { cacheType = 3; } else if (type == TYPE_MASK) { cacheType = 4; } else if (type == TYPE_GREETINGS) { cacheType = 6; } else { cacheType = 5; } SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT document FROM web_recent_v3 WHERE type = " + cacheType + " ORDER BY date DESC"); final ArrayList arrayList = new ArrayList<>(); while (cursor.next()) { if (!cursor.isNull(0)) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Document document = TLRPC.Document.TLdeserialize(data, data.readInt32(false), false); if (document != null) { arrayList.add(document); } data.reuse(); } } } cursor.dispose(); AndroidUtilities.runOnUIThread(() -> { if (gif) { recentGifs = arrayList; loadingRecentGifs = false; recentGifsLoaded = true; } else { recentStickers[type] = arrayList; loadingRecentStickers[type] = false; recentStickersLoaded[type] = true; } if (type == TYPE_GREETINGS) { preloadNextGreetingsSticker(); } getNotificationCenter().postNotificationName(NotificationCenter.recentDocumentsDidLoad, gif, type); loadRecents(type, gif, false, false); }); } catch (Throwable e) { FileLog.e(e); } }); } else { SharedPreferences preferences = MessagesController.getEmojiSettings(currentAccount); if (!force) { long lastLoadTime; if (gif) { lastLoadTime = preferences.getLong("lastGifLoadTime", 0); } else if (type == TYPE_IMAGE) { lastLoadTime = preferences.getLong("lastStickersLoadTime", 0); } else if (type == TYPE_MASK) { lastLoadTime = preferences.getLong("lastStickersLoadTimeMask", 0); } else if (type == TYPE_GREETINGS) { lastLoadTime = preferences.getLong("lastStickersLoadTimeGreet", 0); } else { lastLoadTime = preferences.getLong("lastStickersLoadTimeFavs", 0); } if (Math.abs(System.currentTimeMillis() - lastLoadTime) < 60 * 60 * 1000) { if (gif) { loadingRecentGifs = false; } else { loadingRecentStickers[type] = false; } return; } } if (gif) { TLRPC.TL_messages_getSavedGifs req = new TLRPC.TL_messages_getSavedGifs(); req.hash = calcDocumentsHash(recentGifs); getConnectionsManager().sendRequest(req, (response, error) -> { ArrayList arrayList = null; if (response instanceof TLRPC.TL_messages_savedGifs) { TLRPC.TL_messages_savedGifs res = (TLRPC.TL_messages_savedGifs) response; arrayList = res.gifs; } processLoadedRecentDocuments(type, arrayList, gif, 0, true); }); } else { TLObject request; if (type == TYPE_FAVE) { TLRPC.TL_messages_getFavedStickers req = new TLRPC.TL_messages_getFavedStickers(); req.hash = calcDocumentsHash(recentStickers[type]); request = req; } else if (type == TYPE_GREETINGS) { TLRPC.TL_messages_getStickers req = new TLRPC.TL_messages_getStickers(); req.emoticon = "\uD83D\uDC4B" + Emoji.fixEmoji("⭐"); req.hash = calcDocumentsHash(recentStickers[type]); request = req; } else { TLRPC.TL_messages_getRecentStickers req = new TLRPC.TL_messages_getRecentStickers(); req.hash = calcDocumentsHash(recentStickers[type]); req.attached = type == TYPE_MASK; request = req; } getConnectionsManager().sendRequest(request, (response, error) -> { ArrayList arrayList = null; if (type == TYPE_GREETINGS) { if (response instanceof TLRPC.TL_messages_stickers) { TLRPC.TL_messages_stickers res = (TLRPC.TL_messages_stickers) response; arrayList = res.stickers; } } else if (type == TYPE_FAVE) { if (response instanceof TLRPC.TL_messages_favedStickers) { TLRPC.TL_messages_favedStickers res = (TLRPC.TL_messages_favedStickers) response; arrayList = res.stickers; } } else { if (response instanceof TLRPC.TL_messages_recentStickers) { TLRPC.TL_messages_recentStickers res = (TLRPC.TL_messages_recentStickers) response; arrayList = res.stickers; } } processLoadedRecentDocuments(type, arrayList, false, 0, true); }); } } } private void preloadNextGreetingsSticker() { if (recentStickers[TYPE_GREETINGS].isEmpty()) { return; } greetingsSticker = recentStickers[TYPE_GREETINGS].get(Utilities.random.nextInt(recentStickers[TYPE_GREETINGS].size())); getFileLoader().loadFile(ImageLocation.getForDocument(greetingsSticker), greetingsSticker, null, 0, 1); } public TLRPC.Document getGreetingsSticker() { TLRPC.Document result = greetingsSticker; preloadNextGreetingsSticker(); return result; } protected void processLoadedRecentDocuments(final int type, final ArrayList documents, final boolean gif, final int date, boolean replace) { if (documents != null) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { SQLiteDatabase database = getMessagesStorage().getDatabase(); int maxCount; if (gif) { maxCount = getMessagesController().maxRecentGifsCount; } else { if (type == TYPE_GREETINGS) { maxCount = 200; } else if (type == TYPE_FAVE) { maxCount = getMessagesController().maxFaveStickersCount; } else { maxCount = getMessagesController().maxRecentStickersCount; } } database.beginTransaction(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO web_recent_v3 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); int count = documents.size(); int cacheType; if (gif) { cacheType = 2; } else if (type == TYPE_IMAGE) { cacheType = 3; } else if (type == TYPE_MASK) { cacheType = 4; } else if (type == TYPE_GREETINGS) { cacheType = 6; } else { cacheType = 5; } if (replace) { database.executeFast("DELETE FROM web_recent_v3 WHERE type = " + cacheType).stepThis().dispose(); } for (int a = 0; a < count; a++) { if (a == maxCount) { break; } TLRPC.Document document = documents.get(a); state.requery(); state.bindString(1, "" + document.id); state.bindInteger(2, cacheType); state.bindString(3, ""); state.bindString(4, ""); state.bindString(5, ""); state.bindInteger(6, 0); state.bindInteger(7, 0); state.bindInteger(8, 0); state.bindInteger(9, date != 0 ? date : count - a); NativeByteBuffer data = new NativeByteBuffer(document.getObjectSize()); document.serializeToStream(data); state.bindByteBuffer(10, data); state.step(); data.reuse(); } state.dispose(); database.commitTransaction(); if (documents.size() >= maxCount) { database.beginTransaction(); for (int a = maxCount; a < documents.size(); a++) { database.executeFast("DELETE FROM web_recent_v3 WHERE id = '" + documents.get(a).id + "' AND type = " + cacheType).stepThis().dispose(); } database.commitTransaction(); } } catch (Exception e) { FileLog.e(e); } }); } if (date == 0) { AndroidUtilities.runOnUIThread(() -> { SharedPreferences.Editor editor = MessagesController.getEmojiSettings(currentAccount).edit(); if (gif) { loadingRecentGifs = false; recentGifsLoaded = true; editor.putLong("lastGifLoadTime", System.currentTimeMillis()).commit(); } else { loadingRecentStickers[type] = false; recentStickersLoaded[type] = true; if (type == TYPE_IMAGE) { editor.putLong("lastStickersLoadTime", System.currentTimeMillis()).commit(); } else if (type == TYPE_MASK) { editor.putLong("lastStickersLoadTimeMask", System.currentTimeMillis()).commit(); } else if (type == TYPE_GREETINGS) { editor.putLong("lastStickersLoadTimeGreet", System.currentTimeMillis()).commit(); } else { editor.putLong("lastStickersLoadTimeFavs", System.currentTimeMillis()).commit(); } } if (documents != null) { if (gif) { recentGifs = documents; } else { recentStickers[type] = documents; } if (type == TYPE_GREETINGS) { preloadNextGreetingsSticker(); } getNotificationCenter().postNotificationName(NotificationCenter.recentDocumentsDidLoad, gif, type); } else { } }); } } public void reorderStickers(int type, final ArrayList order) { Collections.sort(stickerSets[type], (lhs, rhs) -> { int index1 = order.indexOf(lhs.set.id); int index2 = order.indexOf(rhs.set.id); if (index1 > index2) { return 1; } else if (index1 < index2) { return -1; } return 0; }); loadHash[type] = calcStickersHash(stickerSets[type]); getNotificationCenter().postNotificationName(NotificationCenter.stickersDidLoad, type); loadStickers(type, false, true); } public void calcNewHash(int type) { loadHash[type] = calcStickersHash(stickerSets[type]); } public void storeTempStickerSet(final TLRPC.TL_messages_stickerSet set) { stickerSetsById.put(set.set.id, set); stickerSetsByName.put(set.set.short_name, set); } public void addNewStickerSet(final TLRPC.TL_messages_stickerSet set) { if (stickerSetsById.indexOfKey(set.set.id) >= 0 || stickerSetsByName.containsKey(set.set.short_name)) { return; } int type = set.set.masks ? TYPE_MASK : TYPE_IMAGE; stickerSets[type].add(0, set); stickerSetsById.put(set.set.id, set); installedStickerSetsById.put(set.set.id, set); stickerSetsByName.put(set.set.short_name, set); LongSparseArray stickersById = new LongSparseArray<>(); for (int a = 0; a < set.documents.size(); a++) { TLRPC.Document document = set.documents.get(a); stickersById.put(document.id, document); } for (int a = 0; a < set.packs.size(); a++) { TLRPC.TL_stickerPack stickerPack = set.packs.get(a); stickerPack.emoticon = stickerPack.emoticon.replace("\uFE0F", ""); ArrayList arrayList = allStickers.get(stickerPack.emoticon); if (arrayList == null) { arrayList = new ArrayList<>(); allStickers.put(stickerPack.emoticon, arrayList); } for (int c = 0; c < stickerPack.documents.size(); c++) { Long id = stickerPack.documents.get(c); if (stickersByEmoji.indexOfKey(id) < 0) { stickersByEmoji.put(id, stickerPack.emoticon); } TLRPC.Document sticker = stickersById.get(id); if (sticker != null) { arrayList.add(sticker); } } } loadHash[type] = calcStickersHash(stickerSets[type]); getNotificationCenter().postNotificationName(NotificationCenter.stickersDidLoad, type); loadStickers(type, false, true); } public void loadFeaturedStickers(boolean cache, boolean force) { if (loadingFeaturedStickers) { return; } loadingFeaturedStickers = true; if (cache) { getMessagesStorage().getStorageQueue().postRunnable(() -> { ArrayList newStickerArray = null; ArrayList unread = new ArrayList<>(); int date = 0; int hash = 0; SQLiteCursor cursor = null; try { cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT data, unread, date, hash FROM stickers_featured WHERE 1"); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { newStickerArray = new ArrayList<>(); int count = data.readInt32(false); for (int a = 0; a < count; a++) { TLRPC.StickerSetCovered stickerSet = TLRPC.StickerSetCovered.TLdeserialize(data, data.readInt32(false), false); newStickerArray.add(stickerSet); } data.reuse(); } data = cursor.byteBufferValue(1); if (data != null) { int count = data.readInt32(false); for (int a = 0; a < count; a++) { unread.add(data.readInt64(false)); } data.reuse(); } date = cursor.intValue(2); hash = calcFeaturedStickersHash(newStickerArray); } } catch (Throwable e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } processLoadedFeaturedStickers(newStickerArray, unread, true, date, hash); }); } else { final TLRPC.TL_messages_getFeaturedStickers req = new TLRPC.TL_messages_getFeaturedStickers(); req.hash = force ? 0 : loadFeaturedHash; getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (response instanceof TLRPC.TL_messages_featuredStickers) { TLRPC.TL_messages_featuredStickers res = (TLRPC.TL_messages_featuredStickers) response; processLoadedFeaturedStickers(res.sets, res.unread, false, (int) (System.currentTimeMillis() / 1000), res.hash); } else { processLoadedFeaturedStickers(null, null, false, (int) (System.currentTimeMillis() / 1000), req.hash); } })); } } private void processLoadedFeaturedStickers(final ArrayList res, final ArrayList unreadStickers, final boolean cache, final int date, final int hash) { AndroidUtilities.runOnUIThread(() -> { loadingFeaturedStickers = false; featuredStickersLoaded = true; }); Utilities.stageQueue.postRunnable(() -> { if (cache && (res == null || Math.abs(System.currentTimeMillis() / 1000 - date) >= 60 * 60) || !cache && res == null && hash == 0) { AndroidUtilities.runOnUIThread(() -> { if (res != null && hash != 0) { loadFeaturedHash = hash; } loadFeaturedStickers(false, false); }, res == null && !cache ? 1000 : 0); if (res == null) { return; } } if (res != null) { try { final ArrayList stickerSetsNew = new ArrayList<>(); final LongSparseArray stickerSetsByIdNew = new LongSparseArray<>(); for (int a = 0; a < res.size(); a++) { TLRPC.StickerSetCovered stickerSet = res.get(a); stickerSetsNew.add(stickerSet); stickerSetsByIdNew.put(stickerSet.set.id, stickerSet); } if (!cache) { putFeaturedStickersToCache(stickerSetsNew, unreadStickers, date, hash); } AndroidUtilities.runOnUIThread(() -> { unreadStickerSets = unreadStickers; featuredStickerSetsById = stickerSetsByIdNew; featuredStickerSets = stickerSetsNew; loadFeaturedHash = hash; loadFeaturedDate = date; loadStickers(TYPE_FEATURED, true, false); getNotificationCenter().postNotificationName(NotificationCenter.featuredStickersDidLoad); }); } catch (Throwable e) { FileLog.e(e); } } else if (!cache) { AndroidUtilities.runOnUIThread(() -> loadFeaturedDate = date); putFeaturedStickersToCache(null, null, date, 0); } }); } private void putFeaturedStickersToCache(ArrayList stickers, final ArrayList unreadStickers, final int date, final int hash) { final ArrayList stickersFinal = stickers != null ? new ArrayList<>(stickers) : null; getMessagesStorage().getStorageQueue().postRunnable(() -> { try { if (stickersFinal != null) { SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO stickers_featured VALUES(?, ?, ?, ?, ?)"); state.requery(); int size = 4; for (int a = 0; a < stickersFinal.size(); a++) { size += stickersFinal.get(a).getObjectSize(); } NativeByteBuffer data = new NativeByteBuffer(size); NativeByteBuffer data2 = new NativeByteBuffer(4 + unreadStickers.size() * 8); data.writeInt32(stickersFinal.size()); for (int a = 0; a < stickersFinal.size(); a++) { stickersFinal.get(a).serializeToStream(data); } data2.writeInt32(unreadStickers.size()); for (int a = 0; a < unreadStickers.size(); a++) { data2.writeInt64(unreadStickers.get(a)); } state.bindInteger(1, 1); state.bindByteBuffer(2, data); state.bindByteBuffer(3, data2); state.bindInteger(4, date); state.bindInteger(5, hash); state.step(); data.reuse(); data2.reuse(); state.dispose(); } else { SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("UPDATE stickers_featured SET date = ?"); state.requery(); state.bindInteger(1, date); state.step(); state.dispose(); } } catch (Exception e) { FileLog.e(e); } }); } private int calcFeaturedStickersHash(ArrayList sets) { long acc = 0; for (int a = 0; a < sets.size(); a++) { TLRPC.StickerSet set = sets.get(a).set; if (set.archived) { continue; } int high_id = (int) (set.id >> 32); int lower_id = (int) set.id; acc = ((acc * 20261) + 0x80000000L + high_id) % 0x80000000L; acc = ((acc * 20261) + 0x80000000L + lower_id) % 0x80000000L; if (unreadStickerSets.contains(set.id)) { acc = ((acc * 20261) + 0x80000000L + 1) % 0x80000000L; } } return (int) acc; } public void markFaturedStickersAsRead(boolean query) { if (unreadStickerSets.isEmpty()) { return; } unreadStickerSets.clear(); loadFeaturedHash = calcFeaturedStickersHash(featuredStickerSets); getNotificationCenter().postNotificationName(NotificationCenter.featuredStickersDidLoad); putFeaturedStickersToCache(featuredStickerSets, unreadStickerSets, loadFeaturedDate, loadFeaturedHash); if (query) { TLRPC.TL_messages_readFeaturedStickers req = new TLRPC.TL_messages_readFeaturedStickers(); getConnectionsManager().sendRequest(req, (response, error) -> { }); } } public int getFeaturesStickersHashWithoutUnread() { long acc = 0; for (int a = 0; a < featuredStickerSets.size(); a++) { TLRPC.StickerSet set = featuredStickerSets.get(a).set; if (set.archived) { continue; } int high_id = (int) (set.id >> 32); int lower_id = (int) set.id; acc = ((acc * 20261) + 0x80000000L + high_id) % 0x80000000L; acc = ((acc * 20261) + 0x80000000L + lower_id) % 0x80000000L; } return (int) acc; } public void markFaturedStickersByIdAsRead(final long id) { if (!unreadStickerSets.contains(id) || readingStickerSets.contains(id)) { return; } readingStickerSets.add(id); TLRPC.TL_messages_readFeaturedStickers req = new TLRPC.TL_messages_readFeaturedStickers(); req.id.add(id); getConnectionsManager().sendRequest(req, (response, error) -> { }); AndroidUtilities.runOnUIThread(() -> { unreadStickerSets.remove(id); readingStickerSets.remove(id); loadFeaturedHash = calcFeaturedStickersHash(featuredStickerSets); getNotificationCenter().postNotificationName(NotificationCenter.featuredStickersDidLoad); putFeaturedStickersToCache(featuredStickerSets, unreadStickerSets, loadFeaturedDate, loadFeaturedHash); }, 1000); } public int getArchivedStickersCount(int type) { return archivedStickersCount[type]; } public void verifyAnimatedStickerMessage(TLRPC.Message message) { verifyAnimatedStickerMessage(message, false); } public void verifyAnimatedStickerMessage(TLRPC.Message message, boolean safe) { if (message == null) { return; } TLRPC.Document document = MessageObject.getDocument(message); String name = MessageObject.getStickerSetName(document); if (TextUtils.isEmpty(name)) { return; } TLRPC.TL_messages_stickerSet stickerSet = stickerSetsByName.get(name); if (stickerSet != null) { for (int a = 0, N = stickerSet.documents.size(); a < N; a++) { TLRPC.Document sticker = stickerSet.documents.get(a); if (sticker.id == document.id && sticker.dc_id == document.dc_id) { message.stickerVerified = 1; break; } } return; } if (safe) { AndroidUtilities.runOnUIThread(() -> verifyAnimatedStickerMessageInternal(message, name)); } else { verifyAnimatedStickerMessageInternal(message, name); } } private void verifyAnimatedStickerMessageInternal(TLRPC.Message message, String name) { ArrayList messages = verifyingMessages.get(name); if (messages == null) { messages = new ArrayList<>(); verifyingMessages.put(name, messages); } messages.add(message); TLRPC.TL_messages_getStickerSet req = new TLRPC.TL_messages_getStickerSet(); req.stickerset = MessageObject.getInputStickerSet(message); getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { ArrayList arrayList = verifyingMessages.get(name); if (response != null) { TLRPC.TL_messages_stickerSet set = (TLRPC.TL_messages_stickerSet) response; storeTempStickerSet(set); for (int b = 0, N2 = arrayList.size(); b < N2; b++) { TLRPC.Message m = arrayList.get(b); TLRPC.Document d = MessageObject.getDocument(m); for (int a = 0, N = set.documents.size(); a < N; a++) { TLRPC.Document sticker = set.documents.get(a); if (sticker.id == d.id && sticker.dc_id == d.dc_id) { m.stickerVerified = 1; break; } } if (m.stickerVerified == 0) { m.stickerVerified = 2; } } } else { for (int b = 0, N2 = arrayList.size(); b < N2; b++) { arrayList.get(b).stickerVerified = 2; } } getNotificationCenter().postNotificationName(NotificationCenter.didVerifyMessagesStickers, arrayList); getMessagesStorage().updateMessageVerifyFlags(arrayList); })); } public void loadArchivedStickersCount(final int type, boolean cache) { if (cache) { SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); int count = preferences.getInt("archivedStickersCount" + type, -1); if (count == -1) { loadArchivedStickersCount(type, false); } else { archivedStickersCount[type] = count; getNotificationCenter().postNotificationName(NotificationCenter.archivedStickersCountDidLoad, type); } } else { TLRPC.TL_messages_getArchivedStickers req = new TLRPC.TL_messages_getArchivedStickers(); req.limit = 0; req.masks = type == TYPE_MASK; int reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (error == null) { TLRPC.TL_messages_archivedStickers res = (TLRPC.TL_messages_archivedStickers) response; archivedStickersCount[type] = res.count; SharedPreferences preferences = MessagesController.getNotificationsSettings(currentAccount); preferences.edit().putInt("archivedStickersCount" + type, res.count).commit(); getNotificationCenter().postNotificationName(NotificationCenter.archivedStickersCountDidLoad, type); } })); } } private void processLoadStickersResponse(final int type, final TLRPC.TL_messages_allStickers res) { final ArrayList newStickerArray = new ArrayList<>(); if (res.sets.isEmpty()) { processLoadedStickers(type, newStickerArray, false, (int) (System.currentTimeMillis() / 1000), res.hash); } else { final LongSparseArray newStickerSets = new LongSparseArray<>(); for (int a = 0; a < res.sets.size(); a++) { final TLRPC.StickerSet stickerSet = res.sets.get(a); TLRPC.TL_messages_stickerSet oldSet = stickerSetsById.get(stickerSet.id); if (oldSet != null && oldSet.set.hash == stickerSet.hash) { oldSet.set.archived = stickerSet.archived; oldSet.set.installed = stickerSet.installed; oldSet.set.official = stickerSet.official; newStickerSets.put(oldSet.set.id, oldSet); newStickerArray.add(oldSet); if (newStickerSets.size() == res.sets.size()) { processLoadedStickers(type, newStickerArray, false, (int) (System.currentTimeMillis() / 1000), res.hash); } continue; } newStickerArray.add(null); final int index = a; TLRPC.TL_messages_getStickerSet req = new TLRPC.TL_messages_getStickerSet(); req.stickerset = new TLRPC.TL_inputStickerSetID(); req.stickerset.id = stickerSet.id; req.stickerset.access_hash = stickerSet.access_hash; getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { TLRPC.TL_messages_stickerSet res1 = (TLRPC.TL_messages_stickerSet) response; newStickerArray.set(index, res1); newStickerSets.put(stickerSet.id, res1); if (newStickerSets.size() == res.sets.size()) { for (int a1 = 0; a1 < newStickerArray.size(); a1++) { if (newStickerArray.get(a1) == null) { newStickerArray.remove(a1); a1--; } } processLoadedStickers(type, newStickerArray, false, (int) (System.currentTimeMillis() / 1000), res.hash); } })); } } } public void loadStickersByEmojiOrName(String name, boolean isEmoji, boolean cache) { if (loadingDiceStickerSets.contains(name) || isEmoji && diceStickerSetsByEmoji.get(name) != null) { return; } loadingDiceStickerSets.add(name); if (cache) { getMessagesStorage().getStorageQueue().postRunnable(() -> { TLRPC.TL_messages_stickerSet stickerSet = null; int date = 0; SQLiteCursor cursor = null; try { cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT data, date FROM stickers_dice WHERE emoji = ?", name); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { stickerSet = TLRPC.TL_messages_stickerSet.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } date = cursor.intValue(1); } } catch (Throwable e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } processLoadedDiceStickers(name, isEmoji, stickerSet, true, date); }); } else { TLRPC.TL_messages_getStickerSet req = new TLRPC.TL_messages_getStickerSet(); if (isEmoji) { TLRPC.TL_inputStickerSetDice inputStickerSetDice = new TLRPC.TL_inputStickerSetDice(); inputStickerSetDice.emoticon = name; req.stickerset = inputStickerSetDice; } else { TLRPC.TL_inputStickerSetShortName inputStickerSetShortName = new TLRPC.TL_inputStickerSetShortName(); inputStickerSetShortName.short_name = name; req.stickerset = inputStickerSetShortName; } getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (BuildConfig.DEBUG && error != null) { //supress test backend warning return; } if (response instanceof TLRPC.TL_messages_stickerSet) { processLoadedDiceStickers(name, isEmoji, (TLRPC.TL_messages_stickerSet) response, false, (int) (System.currentTimeMillis() / 1000)); } else { processLoadedDiceStickers(name, isEmoji, null, false, (int) (System.currentTimeMillis() / 1000)); } })); } } private void processLoadedDiceStickers(String name, boolean isEmoji, TLRPC.TL_messages_stickerSet res, final boolean cache, final int date) { AndroidUtilities.runOnUIThread(() -> loadingDiceStickerSets.remove(name)); Utilities.stageQueue.postRunnable(() -> { if (cache && (res == null || Math.abs(System.currentTimeMillis() / 1000 - date) >= 60 * 60 * 24) || !cache && res == null) { AndroidUtilities.runOnUIThread(() -> loadStickersByEmojiOrName(name, isEmoji, false), res == null && !cache ? 1000 : 0); if (res == null) { return; } } if (res != null) { if (!cache) { putDiceStickersToCache(name, res, date); } AndroidUtilities.runOnUIThread(() -> { diceStickerSetsByEmoji.put(name, res); diceEmojiStickerSetsById.put(res.set.id, name); getNotificationCenter().postNotificationName(NotificationCenter.diceStickersDidLoad, name); }); } else if (!cache) { putDiceStickersToCache(name, null, date); } }); } private void putDiceStickersToCache(final String emoji, TLRPC.TL_messages_stickerSet stickers, final int date) { if (TextUtils.isEmpty(emoji)) { return; } getMessagesStorage().getStorageQueue().postRunnable(() -> { try { if (stickers != null) { SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO stickers_dice VALUES(?, ?, ?)"); state.requery(); NativeByteBuffer data = new NativeByteBuffer(stickers.getObjectSize()); stickers.serializeToStream(data); state.bindString(1, emoji); state.bindByteBuffer(2, data); state.bindInteger(3, date); state.step(); data.reuse(); state.dispose(); } else { SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("UPDATE stickers_dice SET date = ?"); state.requery(); state.bindInteger(1, date); state.step(); state.dispose(); } } catch (Exception e) { FileLog.e(e); } }); } public void loadStickers(final int type, boolean cache, boolean useHash) { loadStickers(type, cache, useHash, false); } public void loadStickers(final int type, boolean cache, boolean force, boolean scheduleIfLoading) { if (loadingStickers[type]) { if (scheduleIfLoading) { scheduledLoadStickers[type] = () -> loadStickers(type, false, force, false); } return; } if (type == TYPE_FEATURED) { if (featuredStickerSets.isEmpty() || !getMessagesController().preloadFeaturedStickers) { return; } } else if (type != TYPE_EMOJI) { loadArchivedStickersCount(type, cache); } loadingStickers[type] = true; if (cache) { getMessagesStorage().getStorageQueue().postRunnable(() -> { ArrayList newStickerArray = null; int date = 0; int hash = 0; SQLiteCursor cursor = null; try { cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT data, date, hash FROM stickers_v2 WHERE id = " + (type + 1)); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { newStickerArray = new ArrayList<>(); int count = data.readInt32(false); for (int a = 0; a < count; a++) { TLRPC.TL_messages_stickerSet stickerSet = TLRPC.TL_messages_stickerSet.TLdeserialize(data, data.readInt32(false), false); newStickerArray.add(stickerSet); } data.reuse(); } date = cursor.intValue(1); hash = calcStickersHash(newStickerArray); } } catch (Throwable e) { FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); } } processLoadedStickers(type, newStickerArray, true, date, hash); }); } else { if (type == TYPE_FEATURED) { TLRPC.TL_messages_allStickers response = new TLRPC.TL_messages_allStickers(); response.hash = loadFeaturedHash; for (int a = 0, size = featuredStickerSets.size(); a < size; a++) { response.sets.add(featuredStickerSets.get(a).set); } processLoadStickersResponse(type, response); } else if (type == TYPE_EMOJI) { TLRPC.TL_messages_getStickerSet req = new TLRPC.TL_messages_getStickerSet(); req.stickerset = new TLRPC.TL_inputStickerSetAnimatedEmoji(); getConnectionsManager().sendRequest(req, (response, error) -> { if (response instanceof TLRPC.TL_messages_stickerSet) { final ArrayList newStickerArray = new ArrayList<>(); newStickerArray.add((TLRPC.TL_messages_stickerSet) response); processLoadedStickers(type, newStickerArray, false, (int) (System.currentTimeMillis() / 1000), calcStickersHash(newStickerArray)); } else { processLoadedStickers(type, null, false, (int) (System.currentTimeMillis() / 1000), 0); } }); } else { TLObject req; final int hash; if (type == TYPE_IMAGE) { req = new TLRPC.TL_messages_getAllStickers(); hash = ((TLRPC.TL_messages_getAllStickers) req).hash = force ? 0 : loadHash[type]; } else { req = new TLRPC.TL_messages_getMaskStickers(); hash = ((TLRPC.TL_messages_getMaskStickers) req).hash = force ? 0 : loadHash[type]; } getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (response instanceof TLRPC.TL_messages_allStickers) { processLoadStickersResponse(type, (TLRPC.TL_messages_allStickers) response); } else { processLoadedStickers(type, null, false, (int) (System.currentTimeMillis() / 1000), hash); } })); } } } private void putStickersToCache(final int type, ArrayList stickers, final int date, final int hash) { final ArrayList stickersFinal = stickers != null ? new ArrayList<>(stickers) : null; getMessagesStorage().getStorageQueue().postRunnable(() -> { try { if (stickersFinal != null) { SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO stickers_v2 VALUES(?, ?, ?, ?)"); state.requery(); int size = 4; for (int a = 0; a < stickersFinal.size(); a++) { size += stickersFinal.get(a).getObjectSize(); } NativeByteBuffer data = new NativeByteBuffer(size); data.writeInt32(stickersFinal.size()); for (int a = 0; a < stickersFinal.size(); a++) { stickersFinal.get(a).serializeToStream(data); } state.bindInteger(1, type + 1); state.bindByteBuffer(2, data); state.bindInteger(3, date); state.bindInteger(4, hash); state.step(); data.reuse(); state.dispose(); } else { SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("UPDATE stickers_v2 SET date = ?"); state.requery(); state.bindInteger(1, date); state.step(); state.dispose(); } } catch (Exception e) { FileLog.e(e); } }); } public String getStickerSetName(long setId) { TLRPC.TL_messages_stickerSet stickerSet = stickerSetsById.get(setId); if (stickerSet != null) { return stickerSet.set.short_name; } TLRPC.StickerSetCovered stickerSetCovered = featuredStickerSetsById.get(setId); if (stickerSetCovered != null) { return stickerSetCovered.set.short_name; } return null; } public static long getStickerSetId(TLRPC.Document document) { for (int a = 0; a < document.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = document.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeSticker) { if (attribute.stickerset instanceof TLRPC.TL_inputStickerSetID) { return attribute.stickerset.id; } break; } } return -1; } public static TLRPC.InputStickerSet getInputStickerSet(TLRPC.Document document) { for (int a = 0; a < document.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = document.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeSticker) { if (attribute.stickerset instanceof TLRPC.TL_inputStickerSetEmpty) { return null; } return attribute.stickerset; } } return null; } private static int calcStickersHash(ArrayList sets) { long acc = 0; for (int a = 0; a < sets.size(); a++) { TLRPC.StickerSet set = sets.get(a).set; if (set.archived) { continue; } acc = ((acc * 20261) + 0x80000000L + set.hash) % 0x80000000L; } return (int) acc; } private void processLoadedStickers(final int type, final ArrayList res, final boolean cache, final int date, final int hash) { AndroidUtilities.runOnUIThread(() -> { loadingStickers[type] = false; stickersLoaded[type] = true; if (scheduledLoadStickers[type] != null) { scheduledLoadStickers[type].run(); scheduledLoadStickers[type] = null; } }); Utilities.stageQueue.postRunnable(() -> { if (cache && (res == null || Math.abs(System.currentTimeMillis() / 1000 - date) >= 60 * 60) || !cache && res == null && hash == 0) { AndroidUtilities.runOnUIThread(() -> { if (res != null && hash != 0) { loadHash[type] = hash; } loadStickers(type, false, false); }, res == null && !cache ? 1000 : 0); if (res == null) { return; } } if (res != null) { try { final ArrayList stickerSetsNew = new ArrayList<>(); final LongSparseArray stickerSetsByIdNew = new LongSparseArray<>(); final HashMap stickerSetsByNameNew = new HashMap<>(); final LongSparseArray stickersByEmojiNew = new LongSparseArray<>(); final LongSparseArray stickersByIdNew = new LongSparseArray<>(); final HashMap> allStickersNew = new HashMap<>(); for (int a = 0; a < res.size(); a++) { TLRPC.TL_messages_stickerSet stickerSet = res.get(a); if (stickerSet == null || removingStickerSetsUndos.indexOfKey(stickerSet.set.id) >= 0) { continue; } stickerSetsNew.add(stickerSet); stickerSetsByIdNew.put(stickerSet.set.id, stickerSet); stickerSetsByNameNew.put(stickerSet.set.short_name, stickerSet); for (int b = 0; b < stickerSet.documents.size(); b++) { TLRPC.Document document = stickerSet.documents.get(b); if (document == null || document instanceof TLRPC.TL_documentEmpty) { continue; } stickersByIdNew.put(document.id, document); } if (!stickerSet.set.archived) { for (int b = 0; b < stickerSet.packs.size(); b++) { TLRPC.TL_stickerPack stickerPack = stickerSet.packs.get(b); if (stickerPack == null || stickerPack.emoticon == null) { continue; } stickerPack.emoticon = stickerPack.emoticon.replace("\uFE0F", ""); ArrayList arrayList = allStickersNew.get(stickerPack.emoticon); if (arrayList == null) { arrayList = new ArrayList<>(); allStickersNew.put(stickerPack.emoticon, arrayList); } for (int c = 0; c < stickerPack.documents.size(); c++) { Long id = stickerPack.documents.get(c); if (stickersByEmojiNew.indexOfKey(id) < 0) { stickersByEmojiNew.put(id, stickerPack.emoticon); } TLRPC.Document sticker = stickersByIdNew.get(id); if (sticker != null) { arrayList.add(sticker); } } } } } if (!cache) { putStickersToCache(type, stickerSetsNew, date, hash); } AndroidUtilities.runOnUIThread(() -> { for (int a = 0; a < stickerSets[type].size(); a++) { TLRPC.StickerSet set = stickerSets[type].get(a).set; stickerSetsById.remove(set.id); stickerSetsByName.remove(set.short_name); if (type != TYPE_FEATURED && type != TYPE_EMOJI) { installedStickerSetsById.remove(set.id); } } for (int a = 0; a < stickerSetsByIdNew.size(); a++) { stickerSetsById.put(stickerSetsByIdNew.keyAt(a), stickerSetsByIdNew.valueAt(a)); if (type != TYPE_FEATURED && type != TYPE_EMOJI) { installedStickerSetsById.put(stickerSetsByIdNew.keyAt(a), stickerSetsByIdNew.valueAt(a)); } } stickerSetsByName.putAll(stickerSetsByNameNew); stickerSets[type] = stickerSetsNew; loadHash[type] = hash; loadDate[type] = date; stickersByIds[type] = stickersByIdNew; if (type == TYPE_IMAGE) { allStickers = allStickersNew; stickersByEmoji = stickersByEmojiNew; } else if (type == TYPE_FEATURED) { allStickersFeatured = allStickersNew; } getNotificationCenter().postNotificationName(NotificationCenter.stickersDidLoad, type); }); } catch (Throwable e) { FileLog.e(e); } } else if (!cache) { AndroidUtilities.runOnUIThread(() -> loadDate[type] = date); putStickersToCache(type, null, date, 0); } }); } public boolean cancelRemovingStickerSet(long id) { final Runnable undoAction = removingStickerSetsUndos.get(id); if (undoAction != null) { undoAction.run(); return true; } else { return false; } } public void preloadStickerSetThumb(TLRPC.TL_messages_stickerSet stickerSet) { TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(stickerSet.set.thumbs, 90); if (thumb != null) { final ArrayList documents = stickerSet.documents; if (documents != null && !documents.isEmpty()) { loadStickerSetThumbInternal(thumb, stickerSet, documents.get(0)); } } } public void preloadStickerSetThumb(TLRPC.StickerSetCovered stickerSet) { TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(stickerSet.set.thumbs, 90); if (thumb != null) { final TLRPC.Document sticker; if (stickerSet.cover != null) { sticker = stickerSet.cover; } else if (!stickerSet.covers.isEmpty()) { sticker = stickerSet.covers.get(0); } else { return; } loadStickerSetThumbInternal(thumb, stickerSet, sticker); } } private void loadStickerSetThumbInternal(TLRPC.PhotoSize thumb, Object parentObject, TLRPC.Document sticker) { final ImageLocation imageLocation = ImageLocation.getForSticker(thumb, sticker); if (imageLocation != null) { final String ext = imageLocation.imageType == FileLoader.IMAGE_TYPE_LOTTIE ? "tgs" : "webp"; getFileLoader().loadFile(imageLocation, parentObject, ext, 2, 1); } } /** @param toggle 0 - remove, 1 - archive, 2 - add */ public void toggleStickerSet(final Context context, final TLObject stickerSetObject, final int toggle, final BaseFragment baseFragment, final boolean showSettings, boolean showTooltip) { final TLRPC.StickerSet stickerSet; final TLRPC.TL_messages_stickerSet messages_stickerSet; if (stickerSetObject instanceof TLRPC.TL_messages_stickerSet) { messages_stickerSet = ((TLRPC.TL_messages_stickerSet) stickerSetObject); stickerSet = messages_stickerSet.set; } else if (stickerSetObject instanceof TLRPC.StickerSetCovered) { stickerSet = ((TLRPC.StickerSetCovered) stickerSetObject).set; if (toggle != 2) { messages_stickerSet = stickerSetsById.get(stickerSet.id); if (messages_stickerSet == null) { return; } } else { messages_stickerSet = null; } } else { throw new IllegalArgumentException("Invalid type of the given stickerSetObject: " + stickerSetObject.getClass()); } final int type = stickerSet.masks ? TYPE_MASK : TYPE_IMAGE; stickerSet.archived = toggle == 1; int currentIndex = 0; for (int a = 0; a < stickerSets[type].size(); a++) { TLRPC.TL_messages_stickerSet set = stickerSets[type].get(a); if (set.set.id == stickerSet.id) { currentIndex = a; stickerSets[type].remove(a); if (toggle == 2) { stickerSets[type].add(0, set); } else { stickerSetsById.remove(set.set.id); installedStickerSetsById.remove(set.set.id); stickerSetsByName.remove(set.set.short_name); } break; } } loadHash[type] = calcStickersHash(stickerSets[type]); putStickersToCache(type, stickerSets[type], loadDate[type], loadHash[type]); getNotificationCenter().postNotificationName(NotificationCenter.stickersDidLoad, type); if (toggle == 2) { if (!cancelRemovingStickerSet(stickerSet.id)) { toggleStickerSetInternal(context, toggle, baseFragment, showSettings, stickerSetObject, stickerSet, type, showTooltip); } } else if (!showTooltip || baseFragment == null) { toggleStickerSetInternal(context, toggle, baseFragment, showSettings, stickerSetObject, stickerSet, type, false); } else { final StickerSetBulletinLayout bulletinLayout = new StickerSetBulletinLayout(context, stickerSetObject, toggle); final int finalCurrentIndex = currentIndex; final Bulletin.UndoButton undoButton = new Bulletin.UndoButton(context, false).setUndoAction(() -> { stickerSet.archived = false; stickerSets[type].add(finalCurrentIndex, messages_stickerSet); stickerSetsById.put(stickerSet.id, messages_stickerSet); installedStickerSetsById.put(stickerSet.id, messages_stickerSet); stickerSetsByName.put(stickerSet.short_name, messages_stickerSet); removingStickerSetsUndos.remove(stickerSet.id); loadHash[type] = calcStickersHash(stickerSets[type]); putStickersToCache(type, stickerSets[type], loadDate[type], loadHash[type]); getNotificationCenter().postNotificationName(NotificationCenter.stickersDidLoad, type); }).setDelayedAction(() -> toggleStickerSetInternal(context, toggle, baseFragment, showSettings, stickerSetObject, stickerSet, type, false)); bulletinLayout.setButton(undoButton); removingStickerSetsUndos.put(stickerSet.id, undoButton::undo); Bulletin.make(baseFragment, bulletinLayout, Bulletin.DURATION_LONG).show(); } } private void toggleStickerSetInternal(Context context, int toggle, BaseFragment baseFragment, boolean showSettings, TLObject stickerSetObject, TLRPC.StickerSet stickerSet, int type, boolean showTooltip) { TLRPC.TL_inputStickerSetID stickerSetID = new TLRPC.TL_inputStickerSetID(); stickerSetID.access_hash = stickerSet.access_hash; stickerSetID.id = stickerSet.id; if (toggle != 0) { TLRPC.TL_messages_installStickerSet req = new TLRPC.TL_messages_installStickerSet(); req.stickerset = stickerSetID; req.archived = toggle == 1; getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { removingStickerSetsUndos.remove(stickerSet.id); if (response instanceof TLRPC.TL_messages_stickerSetInstallResultArchive) { processStickerSetInstallResultArchive(baseFragment, showSettings, type, (TLRPC.TL_messages_stickerSetInstallResultArchive) response); } loadStickers(type, false, false, true); if (error == null && showTooltip && baseFragment != null) { Bulletin.make(baseFragment, new StickerSetBulletinLayout(context, stickerSetObject, StickerSetBulletinLayout.TYPE_ADDED), Bulletin.DURATION_SHORT).show(); } })); } else { TLRPC.TL_messages_uninstallStickerSet req = new TLRPC.TL_messages_uninstallStickerSet(); req.stickerset = stickerSetID; getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { removingStickerSetsUndos.remove(stickerSet.id); loadStickers(type, false, true); })); } } /** @param toggle 0 - uninstall, 1 - archive, 2 - unarchive */ public void toggleStickerSets(final ArrayList stickerSetList, final int type, final int toggle, final BaseFragment baseFragment, final boolean showSettings) { final int stickerSetListSize = stickerSetList.size(); final ArrayList inputStickerSets = new ArrayList<>(stickerSetListSize); for (int i = 0; i < stickerSetListSize; i++) { final TLRPC.StickerSet stickerSet = stickerSetList.get(i); final TLRPC.InputStickerSet inputStickerSet = new TLRPC.TL_inputStickerSetID(); inputStickerSet.access_hash = stickerSet.access_hash; inputStickerSet.id = stickerSet.id; inputStickerSets.add(inputStickerSet); if (toggle != 0) { stickerSet.archived = toggle == 1; } for (int a = 0, size = stickerSets[type].size(); a < size; a++) { TLRPC.TL_messages_stickerSet set = stickerSets[type].get(a); if (set.set.id == inputStickerSet.id) { stickerSets[type].remove(a); if (toggle == 2) { stickerSets[type].add(0, set); } else { stickerSetsById.remove(set.set.id); installedStickerSetsById.remove(set.set.id); stickerSetsByName.remove(set.set.short_name); } break; } } } loadHash[type] = calcStickersHash(this.stickerSets[type]); putStickersToCache(type, this.stickerSets[type], loadDate[type], loadHash[type]); getNotificationCenter().postNotificationName(NotificationCenter.stickersDidLoad, type); final TLRPC.TL_messages_toggleStickerSets req = new TLRPC.TL_messages_toggleStickerSets(); req.stickersets = inputStickerSets; switch (toggle) { case 0: req.uninstall = true; break; case 1: req.archive = true; break; case 2: req.unarchive = true; break; } getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (toggle != 0) { if (response instanceof TLRPC.TL_messages_stickerSetInstallResultArchive) { processStickerSetInstallResultArchive(baseFragment, showSettings, type, (TLRPC.TL_messages_stickerSetInstallResultArchive) response); } loadStickers(type, false, false, true); } else { loadStickers(type, false, true); } })); } public void processStickerSetInstallResultArchive(BaseFragment baseFragment, boolean showSettings, int type, TLRPC.TL_messages_stickerSetInstallResultArchive response) { for (int i = 0, size = response.sets.size(); i < size; i++) { installedStickerSetsById.remove(response.sets.get(i).set.id); } loadArchivedStickersCount(type, false); getNotificationCenter().postNotificationName(NotificationCenter.needAddArchivedStickers, response.sets); if (baseFragment != null && baseFragment.getParentActivity() != null) { final StickersArchiveAlert alert = new StickersArchiveAlert(baseFragment.getParentActivity(), showSettings ? baseFragment : null, response.sets); baseFragment.showDialog(alert.create()); } } //---------------- STICKERS END ---------------- private int reqId; private int mergeReqId; private long lastMergeDialogId; private int lastReplyMessageId; private long lastDialogId; private int lastReqId; private int lastGuid; private TLRPC.User lastSearchUser; private TLRPC.Chat lastSearchChat; private int[] messagesSearchCount = new int[]{0, 0}; private boolean[] messagesSearchEndReached = new boolean[]{false, false}; private ArrayList searchResultMessages = new ArrayList<>(); private SparseArray[] searchResultMessagesMap = new SparseArray[]{new SparseArray<>(), new SparseArray<>()}; private String lastSearchQuery; private int lastReturnedNum; private boolean loadingMoreSearchMessages; private int getMask() { int mask = 0; if (lastReturnedNum < searchResultMessages.size() - 1 || !messagesSearchEndReached[0] || !messagesSearchEndReached[1]) { mask |= 1; } if (lastReturnedNum > 0) { mask |= 2; } return mask; } public ArrayList getFoundMessageObjects() { return searchResultMessages; } public void clearFoundMessageObjects() { searchResultMessages.clear(); } public boolean isMessageFound(final int messageId, boolean mergeDialog) { return searchResultMessagesMap[mergeDialog ? 1 : 0].indexOfKey(messageId) >= 0; } public void searchMessagesInChat(String query, final long dialogId, final long mergeDialogId, final int guid, final int direction, int replyMessageId, TLRPC.User user, TLRPC.Chat chat) { searchMessagesInChat(query, dialogId, mergeDialogId, guid, direction, replyMessageId, false, user, chat, true); } public void jumpToSearchedMessage(int guid, int index) { if (index < 0 || index >= searchResultMessages.size()) { return; } lastReturnedNum = index; MessageObject messageObject = searchResultMessages.get(lastReturnedNum); getNotificationCenter().postNotificationName(NotificationCenter.chatSearchResultsAvailable, guid, messageObject.getId(), getMask(), messageObject.getDialogId(), lastReturnedNum, messagesSearchCount[0] + messagesSearchCount[1], true); } public void loadMoreSearchMessages() { if (loadingMoreSearchMessages || messagesSearchEndReached[0] && lastMergeDialogId == 0 && messagesSearchEndReached[1]) { return; } int temp = searchResultMessages.size(); lastReturnedNum = searchResultMessages.size(); searchMessagesInChat(null, lastDialogId, lastMergeDialogId, lastGuid, 1, lastReplyMessageId, false, lastSearchUser, lastSearchChat, false); lastReturnedNum = temp; loadingMoreSearchMessages = true; } private void searchMessagesInChat(String query, final long dialogId, final long mergeDialogId, final int guid, final int direction, int replyMessageId, final boolean internal, final TLRPC.User user, final TLRPC.Chat chat, boolean jumpToMessage) { int max_id = 0; long queryWithDialog = dialogId; boolean firstQuery = !internal; if (reqId != 0) { getConnectionsManager().cancelRequest(reqId, true); reqId = 0; } if (mergeReqId != 0) { getConnectionsManager().cancelRequest(mergeReqId, true); mergeReqId = 0; } if (query == null) { if (searchResultMessages.isEmpty()) { return; } if (direction == 1) { lastReturnedNum++; if (lastReturnedNum < searchResultMessages.size()) { MessageObject messageObject = searchResultMessages.get(lastReturnedNum); getNotificationCenter().postNotificationName(NotificationCenter.chatSearchResultsAvailable, guid, messageObject.getId(), getMask(), messageObject.getDialogId(), lastReturnedNum, messagesSearchCount[0] + messagesSearchCount[1], jumpToMessage); return; } else { if (messagesSearchEndReached[0] && mergeDialogId == 0 && messagesSearchEndReached[1]) { lastReturnedNum--; return; } firstQuery = false; query = lastSearchQuery; MessageObject messageObject = searchResultMessages.get(searchResultMessages.size() - 1); if (messageObject.getDialogId() == dialogId && !messagesSearchEndReached[0]) { max_id = messageObject.getId(); queryWithDialog = dialogId; } else { if (messageObject.getDialogId() == mergeDialogId) { max_id = messageObject.getId(); } queryWithDialog = mergeDialogId; messagesSearchEndReached[1] = false; } } } else if (direction == 2) { lastReturnedNum--; if (lastReturnedNum < 0) { lastReturnedNum = 0; return; } if (lastReturnedNum >= searchResultMessages.size()) { lastReturnedNum = searchResultMessages.size() - 1; } MessageObject messageObject = searchResultMessages.get(lastReturnedNum); getNotificationCenter().postNotificationName(NotificationCenter.chatSearchResultsAvailable, guid, messageObject.getId(), getMask(), messageObject.getDialogId(), lastReturnedNum, messagesSearchCount[0] + messagesSearchCount[1], jumpToMessage); return; } else { return; } } else if (firstQuery) { messagesSearchEndReached[0] = messagesSearchEndReached[1] = false; messagesSearchCount[0] = messagesSearchCount[1] = 0; searchResultMessages.clear(); searchResultMessagesMap[0].clear(); searchResultMessagesMap[1].clear(); getNotificationCenter().postNotificationName(NotificationCenter.chatSearchResultsLoading, guid); } if (messagesSearchEndReached[0] && !messagesSearchEndReached[1] && mergeDialogId != 0) { queryWithDialog = mergeDialogId; } if (queryWithDialog == dialogId && firstQuery) { if (mergeDialogId != 0) { TLRPC.InputPeer inputPeer = getMessagesController().getInputPeer((int) mergeDialogId); if (inputPeer == null) { return; } final TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); req.peer = inputPeer; lastMergeDialogId = mergeDialogId; req.limit = 1; req.q = query; if (user != null) { req.from_id = MessagesController.getInputPeer(user); req.flags |= 1; } else if (chat != null) { req.from_id = MessagesController.getInputPeer(chat); req.flags |= 1; } req.filter = new TLRPC.TL_inputMessagesFilterEmpty(); mergeReqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (lastMergeDialogId == mergeDialogId) { mergeReqId = 0; if (response != null) { TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; messagesSearchEndReached[1] = res.messages.isEmpty(); messagesSearchCount[1] = res instanceof TLRPC.TL_messages_messagesSlice ? res.count : res.messages.size(); searchMessagesInChat(req.q, dialogId, mergeDialogId, guid, direction, replyMessageId, true, user, chat, jumpToMessage); } else { messagesSearchEndReached[1] = true; messagesSearchCount[1] = 0; searchMessagesInChat(req.q, dialogId, mergeDialogId, guid, direction, replyMessageId, true, user, chat, jumpToMessage); } } }), ConnectionsManager.RequestFlagFailOnServerErrors); return; } else { lastMergeDialogId = 0; messagesSearchEndReached[1] = true; messagesSearchCount[1] = 0; } } final TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); req.peer = getMessagesController().getInputPeer((int) queryWithDialog); if (req.peer == null) { return; } lastGuid = guid; lastDialogId = dialogId; lastSearchUser = user; lastSearchChat = chat; lastReplyMessageId = replyMessageId; req.limit = 21; req.q = query != null ? query : ""; req.offset_id = max_id; if (user != null) { req.from_id = MessagesController.getInputPeer(user); req.flags |= 1; } else if (chat != null) { req.from_id = MessagesController.getInputPeer(chat); req.flags |= 1; } if (lastReplyMessageId != 0) { req.top_msg_id = lastReplyMessageId; req.flags |= 2; } req.filter = new TLRPC.TL_inputMessagesFilterEmpty(); final int currentReqId = ++lastReqId; lastSearchQuery = query; final long queryWithDialogFinal = queryWithDialog; String finalQuery = query; reqId = getConnectionsManager().sendRequest(req, (response, error) -> { ArrayList messageObjects = new ArrayList<>(); if (error == null) { TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; int N = Math.min(res.messages.size(), 20); for (int a = 0; a < N; a++) { TLRPC.Message message = res.messages.get(a); MessageObject messageObject = new MessageObject(currentAccount, message, false, false); messageObject.setQuery(finalQuery); messageObjects.add(messageObject); } } AndroidUtilities.runOnUIThread(() -> { if (currentReqId == lastReqId) { reqId = 0; if (!jumpToMessage) { loadingMoreSearchMessages = false; } if (response != null) { TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; for (int a = 0; a < res.messages.size(); a++) { TLRPC.Message message = res.messages.get(a); if (message instanceof TLRPC.TL_messageEmpty || message.action instanceof TLRPC.TL_messageActionHistoryClear) { res.messages.remove(a); a--; } } getMessagesStorage().putUsersAndChats(res.users, res.chats, true, true); getMessagesController().putUsers(res.users, false); getMessagesController().putChats(res.chats, false); if (req.offset_id == 0 && queryWithDialogFinal == dialogId) { lastReturnedNum = 0; searchResultMessages.clear(); searchResultMessagesMap[0].clear(); searchResultMessagesMap[1].clear(); messagesSearchCount[0] = 0; getNotificationCenter().postNotificationName(NotificationCenter.chatSearchResultsLoading, guid); } boolean added = false; int N = Math.min(res.messages.size(), 20); for (int a = 0; a < N; a++) { TLRPC.Message message = res.messages.get(a); added = true; MessageObject messageObject = messageObjects.get(a); searchResultMessages.add(messageObject); searchResultMessagesMap[queryWithDialogFinal == dialogId ? 0 : 1].put(messageObject.getId(), messageObject); } messagesSearchEndReached[queryWithDialogFinal == dialogId ? 0 : 1] = res.messages.size() < 21; messagesSearchCount[queryWithDialogFinal == dialogId ? 0 : 1] = res instanceof TLRPC.TL_messages_messagesSlice || res instanceof TLRPC.TL_messages_channelMessages ? res.count : res.messages.size(); if (searchResultMessages.isEmpty()) { getNotificationCenter().postNotificationName(NotificationCenter.chatSearchResultsAvailable, guid, 0, getMask(), (long) 0, 0, 0, jumpToMessage); } else { if (added) { if (lastReturnedNum >= searchResultMessages.size()) { lastReturnedNum = searchResultMessages.size() - 1; } MessageObject messageObject = searchResultMessages.get(lastReturnedNum); getNotificationCenter().postNotificationName(NotificationCenter.chatSearchResultsAvailable, guid, messageObject.getId(), getMask(), messageObject.getDialogId(), lastReturnedNum, messagesSearchCount[0] + messagesSearchCount[1], jumpToMessage); } } if (queryWithDialogFinal == dialogId && messagesSearchEndReached[0] && mergeDialogId != 0 && !messagesSearchEndReached[1]) { searchMessagesInChat(lastSearchQuery, dialogId, mergeDialogId, guid, 0, replyMessageId, true, user, chat, jumpToMessage); } } } }); }, ConnectionsManager.RequestFlagFailOnServerErrors); } public String getLastSearchQuery() { return lastSearchQuery; } //---------------- MESSAGE SEARCH END ---------------- public final static int MEDIA_PHOTOVIDEO = 0; public final static int MEDIA_FILE = 1; public final static int MEDIA_AUDIO = 2; public final static int MEDIA_URL = 3; public final static int MEDIA_MUSIC = 4; public final static int MEDIA_GIF = 5; public final static int MEDIA_TYPES_COUNT = 6; public void loadMedia(long uid, int count, int max_id, int type, int fromCache, int classGuid) { final boolean isChannel = (int) uid < 0 && ChatObject.isChannel(-(int) uid, currentAccount); if (BuildVars.LOGS_ENABLED) { FileLog.d("load media did " + uid + " count = " + count + " max_id " + max_id + " type = " + type + " cache = " + fromCache + " classGuid = " + classGuid); } int lower_part = (int)uid; if (fromCache != 0 || lower_part == 0) { loadMediaDatabase(uid, count, max_id, type, classGuid, isChannel, fromCache); } else { TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); req.limit = count; req.offset_id = max_id; if (type == MEDIA_PHOTOVIDEO) { req.filter = new TLRPC.TL_inputMessagesFilterPhotoVideo(); } else if (type == MEDIA_FILE) { req.filter = new TLRPC.TL_inputMessagesFilterDocument(); } else if (type == MEDIA_AUDIO) { req.filter = new TLRPC.TL_inputMessagesFilterRoundVoice(); } else if (type == MEDIA_URL) { req.filter = new TLRPC.TL_inputMessagesFilterUrl(); } else if (type == MEDIA_MUSIC) { req.filter = new TLRPC.TL_inputMessagesFilterMusic(); } else if (type == MEDIA_GIF) { req.filter = new TLRPC.TL_inputMessagesFilterGif(); } req.q = ""; req.peer = getMessagesController().getInputPeer(lower_part); if (req.peer == null) { return; } int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; getMessagesController().removeDeletedMessagesFromArray(uid, res.messages); processLoadedMedia(res, uid, count, max_id, type, 0, classGuid, isChannel, res.messages.size() == 0); } }); getConnectionsManager().bindRequestToGuid(reqId, classGuid); } } public void getMediaCounts(final long uid, final int classGuid) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { int[] counts = new int[] {-1, -1, -1, -1, -1, -1}; int[] countsFinal = new int[] {-1, -1, -1, -1, -1, -1}; int[] old = new int[] {0, 0, 0, 0, 0, 0}; SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT type, count, old FROM media_counts_v2 WHERE uid = %d", uid)); while (cursor.next()) { int type = cursor.intValue(0); if (type >= 0 && type < MEDIA_TYPES_COUNT) { countsFinal[type] = counts[type] = cursor.intValue(1); old[type] = cursor.intValue(2); } } cursor.dispose(); int lower_part = (int) uid; if (lower_part == 0) { for (int a = 0; a < counts.length; a++) { if (counts[a] == -1) { cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM media_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, a)); if (cursor.next()) { counts[a] = cursor.intValue(0); } else { counts[a] = 0; } cursor.dispose(); putMediaCountDatabase(uid, a, counts[a]); } } AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.mediaCountsDidLoad, uid, counts)); } else { boolean missing = false; TLRPC.TL_messages_getSearchCounters req = new TLRPC.TL_messages_getSearchCounters(); req.peer = getMessagesController().getInputPeer(lower_part); for (int a = 0; a < counts.length; a++) { if (req.peer == null) { counts[a] = 0; continue; } if (counts[a] == -1 || old[a] == 1) { if (a == MEDIA_PHOTOVIDEO) { req.filters.add(new TLRPC.TL_inputMessagesFilterPhotoVideo()); } else if (a == MEDIA_FILE) { req.filters.add(new TLRPC.TL_inputMessagesFilterDocument()); } else if (a == MEDIA_AUDIO) { req.filters.add(new TLRPC.TL_inputMessagesFilterRoundVoice()); } else if (a == MEDIA_URL) { req.filters.add(new TLRPC.TL_inputMessagesFilterUrl()); } else if (a == MEDIA_MUSIC) { req.filters.add(new TLRPC.TL_inputMessagesFilterMusic()); } else { req.filters.add(new TLRPC.TL_inputMessagesFilterGif()); } if (counts[a] == -1) { missing = true; } else if (old[a] == 1) { counts[a] = -1; } } } if (!req.filters.isEmpty()) { int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { Arrays.fill(counts, 0); if (response != null) { TLRPC.Vector res = (TLRPC.Vector) response; for (int a = 0, N = res.objects.size(); a < N; a++) { TLRPC.TL_messages_searchCounter searchCounter = (TLRPC.TL_messages_searchCounter) res.objects.get(a); int type; if (searchCounter.filter instanceof TLRPC.TL_inputMessagesFilterPhotoVideo) { type = MEDIA_PHOTOVIDEO; } else if (searchCounter.filter instanceof TLRPC.TL_inputMessagesFilterDocument) { type = MEDIA_FILE; } else if (searchCounter.filter instanceof TLRPC.TL_inputMessagesFilterRoundVoice) { type = MEDIA_AUDIO; } else if (searchCounter.filter instanceof TLRPC.TL_inputMessagesFilterUrl) { type = MEDIA_URL; } else if (searchCounter.filter instanceof TLRPC.TL_inputMessagesFilterMusic) { type = MEDIA_MUSIC; } else if (searchCounter.filter instanceof TLRPC.TL_inputMessagesFilterGif) { type = MEDIA_GIF; } else { continue; } counts[type] = searchCounter.count; putMediaCountDatabase(uid, type, counts[type]); } } AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.mediaCountsDidLoad, uid, counts)); }); getConnectionsManager().bindRequestToGuid(reqId, classGuid); } if (!missing) { AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.mediaCountsDidLoad, uid, countsFinal)); } } } catch (Exception e) { FileLog.e(e); } }); } public void getMediaCount(final long uid, final int type, final int classGuid, boolean fromCache) { int lower_part = (int)uid; if (fromCache || lower_part == 0) { getMediaCountDatabase(uid, type, classGuid); } else { TLRPC.TL_messages_getSearchCounters req = new TLRPC.TL_messages_getSearchCounters(); if (type == MEDIA_PHOTOVIDEO) { req.filters.add(new TLRPC.TL_inputMessagesFilterPhotoVideo()); } else if (type == MEDIA_FILE) { req.filters.add(new TLRPC.TL_inputMessagesFilterDocument()); } else if (type == MEDIA_AUDIO) { req.filters.add(new TLRPC.TL_inputMessagesFilterRoundVoice()); } else if (type == MEDIA_URL) { req.filters.add(new TLRPC.TL_inputMessagesFilterUrl()); } else if (type == MEDIA_MUSIC) { req.filters.add(new TLRPC.TL_inputMessagesFilterMusic()); } else if (type == MEDIA_GIF) { req.filters.add(new TLRPC.TL_inputMessagesFilterGif()); } req.peer = getMessagesController().getInputPeer(lower_part); if (req.peer == null) { return; } int reqId = getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { final TLRPC.Vector res = (TLRPC.Vector) response; if (!res.objects.isEmpty()) { TLRPC.TL_messages_searchCounter counter = (TLRPC.TL_messages_searchCounter) res.objects.get(0); processLoadedMediaCount(counter.count, uid, type, classGuid, false, 0); } } }); getConnectionsManager().bindRequestToGuid(reqId, classGuid); } } public static int getMediaType(TLRPC.Message message) { if (message == null) { return -1; } if (message.media instanceof TLRPC.TL_messageMediaPhoto) { return MEDIA_PHOTOVIDEO; } else if (message.media instanceof TLRPC.TL_messageMediaDocument) { TLRPC.Document document = message.media.document; if (document == null) { return -1; } boolean isAnimated = false; boolean isVideo = false; boolean isVoice = false; boolean isMusic = false; boolean isSticker = false; for (int a = 0; a < document.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = document.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeVideo) { isVoice = attribute.round_message; isVideo = !attribute.round_message; } else if (attribute instanceof TLRPC.TL_documentAttributeAnimated) { isAnimated = true; } else if (attribute instanceof TLRPC.TL_documentAttributeAudio) { isVoice = attribute.voice; isMusic = !attribute.voice; } else if (attribute instanceof TLRPC.TL_documentAttributeSticker) { isSticker = true; } } if (isVoice) { return MEDIA_AUDIO; } else if (isVideo && !isAnimated) { return MEDIA_PHOTOVIDEO; } else if (isSticker) { return -1; } else if (isAnimated) { return MEDIA_GIF; } else if (isMusic) { return MEDIA_MUSIC; } else { return MEDIA_FILE; } } else if (!message.entities.isEmpty()) { for (int a = 0; a < message.entities.size(); a++) { TLRPC.MessageEntity entity = message.entities.get(a); if (entity instanceof TLRPC.TL_messageEntityUrl || entity instanceof TLRPC.TL_messageEntityTextUrl || entity instanceof TLRPC.TL_messageEntityEmail) { return MEDIA_URL; } } } return -1; } public static boolean canAddMessageToMedia(TLRPC.Message message) { if (message instanceof TLRPC.TL_message_secret && (message.media instanceof TLRPC.TL_messageMediaPhoto || MessageObject.isVideoMessage(message) || MessageObject.isGifMessage(message)) && message.media.ttl_seconds != 0 && message.media.ttl_seconds <= 60) { return false; } else if (!(message instanceof TLRPC.TL_message_secret) && message instanceof TLRPC.TL_message && (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaDocument) && message.media.ttl_seconds != 0) { return false; } else { return getMediaType(message) != -1; } } private void processLoadedMedia(final TLRPC.messages_Messages res, final long uid, int count, int max_id, final int type, final int fromCache, final int classGuid, final boolean isChannel, final boolean topReached) { if (BuildVars.LOGS_ENABLED) { FileLog.d("process load media did " + uid + " count = " + count + " max_id " + max_id + " type = " + type + " cache = " + fromCache + " classGuid = " + classGuid); } int lower_part = (int)uid; if (fromCache != 0 && res.messages.isEmpty() && lower_part != 0) { if (fromCache == 2) { return; } loadMedia(uid, count, max_id, type, 0, classGuid); } else { if (fromCache == 0) { ImageLoader.saveMessagesThumbs(res.messages); getMessagesStorage().putUsersAndChats(res.users, res.chats, true, true); putMediaDatabase(uid, type, res.messages, max_id, topReached); } Utilities.searchQueue.postRunnable(() -> { final SparseArray usersDict = new SparseArray<>(); for (int a = 0; a < res.users.size(); a++) { TLRPC.User u = res.users.get(a); usersDict.put(u.id, u); } final ArrayList objects = new ArrayList<>(); for (int a = 0; a < res.messages.size(); a++) { TLRPC.Message message = res.messages.get(a); objects.add(new MessageObject(currentAccount, message, usersDict, true, true)); } AndroidUtilities.runOnUIThread(() -> { int totalCount = res.count; getMessagesController().putUsers(res.users, fromCache != 0); getMessagesController().putChats(res.chats, fromCache != 0); getNotificationCenter().postNotificationName(NotificationCenter.mediaDidLoad, uid, totalCount, objects, classGuid, type, topReached); }); }); } } private void processLoadedMediaCount(final int count, final long uid, final int type, final int classGuid, final boolean fromCache, int old) { AndroidUtilities.runOnUIThread(() -> { int lower_part = (int) uid; boolean reload = fromCache && (count == -1 || count == 0 && type == 2) && lower_part != 0; if (reload || old == 1 && lower_part != 0) { getMediaCount(uid, type, classGuid, false); } if (!reload) { if (!fromCache) { putMediaCountDatabase(uid, type, count); } getNotificationCenter().postNotificationName(NotificationCenter.mediaCountDidLoad, uid, (fromCache && count == -1 ? 0 : count), fromCache, type); } }); } private void putMediaCountDatabase(final long uid, final int type, final int count) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { SQLitePreparedStatement state2 = getMessagesStorage().getDatabase().executeFast("REPLACE INTO media_counts_v2 VALUES(?, ?, ?, ?)"); state2.requery(); state2.bindLong(1, uid); state2.bindInteger(2, type); state2.bindInteger(3, count); state2.bindInteger(4, 0); state2.step(); state2.dispose(); } catch (Exception e) { FileLog.e(e); } }); } private void getMediaCountDatabase(final long uid, final int type, final int classGuid) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { int count = -1; int old = 0; SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT count, old FROM media_counts_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type)); if (cursor.next()) { count = cursor.intValue(0); old = cursor.intValue(1); } cursor.dispose(); int lower_part = (int)uid; if (count == -1 && lower_part == 0) { cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM media_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type)); if (cursor.next()) { count = cursor.intValue(0); } cursor.dispose(); if (count != -1) { putMediaCountDatabase(uid, type, count); } } processLoadedMediaCount(count, uid, type, classGuid, true, old); } catch (Exception e) { FileLog.e(e); } }); } private void loadMediaDatabase(final long uid, final int count, final int max_id, final int type, final int classGuid, final boolean isChannel, final int fromCache) { Runnable runnable = new Runnable() { @Override public void run() { boolean topReached = false; TLRPC.TL_messages_messages res = new TLRPC.TL_messages_messages(); try { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); int countToLoad = count + 1; SQLiteCursor cursor; SQLiteDatabase database = getMessagesStorage().getDatabase(); boolean isEnd = false; if ((int) uid != 0) { int channelId = 0; long messageMaxId = max_id; if (isChannel) { channelId = -(int) uid; } if (messageMaxId != 0 && channelId != 0) { messageMaxId |= ((long) channelId) << 32; } cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM media_holes_v2 WHERE uid = %d AND type = %d AND start IN (0, 1)", uid, type)); if (cursor.next()) { isEnd = cursor.intValue(0) == 1; } else { cursor.dispose(); cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM media_v2 WHERE uid = %d AND type = %d AND mid > 0", uid, type)); if (cursor.next()) { int mid = cursor.intValue(0); if (mid != 0) { SQLitePreparedStatement state = database.executeFast("REPLACE INTO media_holes_v2 VALUES(?, ?, ?, ?)"); state.requery(); state.bindLong(1, uid); state.bindInteger(2, type); state.bindInteger(3, 0); state.bindInteger(4, mid); state.step(); state.dispose(); } } } cursor.dispose(); long holeMessageId = 0; if (messageMaxId != 0) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT end FROM media_holes_v2 WHERE uid = %d AND type = %d AND end <= %d ORDER BY end DESC LIMIT 1", uid, type, max_id)); if (cursor.next()) { holeMessageId = cursor.intValue(0); if (channelId != 0) { holeMessageId |= ((long) channelId) << 32; } } cursor.dispose(); if (holeMessageId > 1) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid > 0 AND mid < %d AND mid >= %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, messageMaxId, holeMessageId, type, countToLoad)); } else { cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid > 0 AND mid < %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, messageMaxId, type, countToLoad)); } } else { cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(end) FROM media_holes_v2 WHERE uid = %d AND type = %d", uid, type)); if (cursor.next()) { holeMessageId = cursor.intValue(0); if (channelId != 0) { holeMessageId |= ((long) channelId) << 32; } } cursor.dispose(); if (holeMessageId > 1) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid >= %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, holeMessageId, type, countToLoad)); } else { cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid > 0 AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, type, countToLoad)); } } } else { isEnd = true; if (max_id != 0) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_v2 as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d AND type = %d ORDER BY m.mid ASC LIMIT %d", uid, max_id, type, countToLoad)); } else { cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_v2 as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND type = %d ORDER BY m.mid ASC LIMIT %d", uid, type, countToLoad)); } } while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); message.id = cursor.intValue(1); message.dialog_id = uid; if ((int) uid == 0) { message.random_id = cursor.longValue(2); } res.messages.add(message); MessagesStorage.addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); } } cursor.dispose(); if (!usersToLoad.isEmpty()) { getMessagesStorage().getUsersInternal(TextUtils.join(",", usersToLoad), res.users); } if (!chatsToLoad.isEmpty()) { getMessagesStorage().getChatsInternal(TextUtils.join(",", chatsToLoad), res.chats); } if (res.messages.size() > count) { topReached = false; res.messages.remove(res.messages.size() - 1); } else { topReached = isEnd; } } catch (Exception e) { res.messages.clear(); res.chats.clear(); res.users.clear(); FileLog.e(e); } finally { Runnable task = this; AndroidUtilities.runOnUIThread(() -> getMessagesStorage().completeTaskForGuid(task, classGuid)); processLoadedMedia(res, uid, count, max_id, type, fromCache, classGuid, isChannel, topReached); } } }; MessagesStorage messagesStorage = getMessagesStorage(); messagesStorage.getStorageQueue().postRunnable(runnable); messagesStorage.bindTaskToGuid(runnable, classGuid); } private void putMediaDatabase(final long uid, final int type, final ArrayList messages, final int max_id, final boolean topReached) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { if (messages.isEmpty() || topReached) { getMessagesStorage().doneHolesInMedia(uid, max_id, type); if (messages.isEmpty()) { return; } } getMessagesStorage().getDatabase().beginTransaction(); SQLitePreparedStatement state2 = getMessagesStorage().getDatabase().executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); for (TLRPC.Message message : messages) { if (canAddMessageToMedia(message)) { long messageId = message.id; if (message.peer_id.channel_id != 0) { messageId |= ((long) message.peer_id.channel_id) << 32; } state2.requery(); NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); state2.bindLong(1, messageId); state2.bindLong(2, uid); state2.bindInteger(3, message.date); state2.bindInteger(4, type); state2.bindByteBuffer(5, data); state2.step(); data.reuse(); } } state2.dispose(); if (!topReached || max_id != 0) { int minId = topReached ? 1 : messages.get(messages.size() - 1).id; if (max_id != 0) { getMessagesStorage().closeHolesInMedia(uid, minId, max_id, type); } else { getMessagesStorage().closeHolesInMedia(uid, minId, Integer.MAX_VALUE, type); } } getMessagesStorage().getDatabase().commitTransaction(); } catch (Exception e) { FileLog.e(e); } }); } public void loadMusic(final long uid, final long maxId, final long minId) { getMessagesStorage().getStorageQueue().postRunnable(() -> { final ArrayList arrayListBegin = new ArrayList<>(); final ArrayList arrayListEnd = new ArrayList<>(); try { int lower_id = (int) uid; for (int a = 0; a < 2; a++) { ArrayList arrayList = a == 0 ? arrayListBegin : arrayListEnd; SQLiteCursor cursor; if (a == 0) { if (lower_id != 0) { cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid < %d AND type = %d ORDER BY date DESC, mid DESC LIMIT 1000", uid, maxId, MEDIA_MUSIC)); } else { cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid > %d AND type = %d ORDER BY date DESC, mid DESC LIMIT 1000", uid, maxId, MEDIA_MUSIC)); } } else { if (lower_id != 0) { cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid > %d AND type = %d ORDER BY date DESC, mid DESC LIMIT 1000", uid, minId, MEDIA_MUSIC)); } else { cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid < %d AND type = %d ORDER BY date DESC, mid DESC LIMIT 1000", uid, minId, MEDIA_MUSIC)); } } while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (MessageObject.isMusicMessage(message)) { message.id = cursor.intValue(1); message.dialog_id = uid; arrayList.add(0, new MessageObject(currentAccount, message, false, true)); } } } cursor.dispose(); } } catch (Exception e) { FileLog.e(e); } AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.musicDidLoad, uid, arrayListBegin, arrayListEnd)); }); } //---------------- MEDIA END ---------------- public ArrayList hints = new ArrayList<>(); public ArrayList inlineBots = new ArrayList<>(); boolean loaded; boolean loading; private static Paint roundPaint, erasePaint; private static RectF bitmapRect; private static Path roundPath; public void buildShortcuts() { if (Build.VERSION.SDK_INT < 23) { return; } int maxShortcuts = ShortcutManagerCompat.getMaxShortcutCountPerActivity(ApplicationLoader.applicationContext) - 2; if (maxShortcuts <= 0) { maxShortcuts = 5; } final ArrayList hintsFinal = new ArrayList<>(); if (SharedConfig.passcodeHash.length() <= 0) { for (int a = 0; a < hints.size(); a++) { hintsFinal.add(hints.get(a)); if (hintsFinal.size() == maxShortcuts - 2) { break; } } } Utilities.globalQueue.postRunnable(() -> { try { if (SharedConfig.directShareHash == null) { SharedConfig.directShareHash = UUID.randomUUID().toString(); ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE).edit().putString("directShareHash2", SharedConfig.directShareHash).commit(); } List currentShortcuts = ShortcutManagerCompat.getDynamicShortcuts(ApplicationLoader.applicationContext); ArrayList shortcutsToUpdate = new ArrayList<>(); ArrayList newShortcutsIds = new ArrayList<>(); ArrayList shortcutsToDelete = new ArrayList<>(); if (currentShortcuts != null && !currentShortcuts.isEmpty()) { newShortcutsIds.add("compose"); for (int a = 0; a < hintsFinal.size(); a++) { TLRPC.TL_topPeer hint = hintsFinal.get(a); long did; if (hint.peer.user_id != 0) { did = hint.peer.user_id; } else { did = -hint.peer.chat_id; if (did == 0) { did = -hint.peer.channel_id; } } newShortcutsIds.add("did3_" + did); } for (int a = 0; a < currentShortcuts.size(); a++) { String id = currentShortcuts.get(a).getId(); if (!newShortcutsIds.remove(id)) { shortcutsToDelete.add(id); } shortcutsToUpdate.add(id); } if (newShortcutsIds.isEmpty() && shortcutsToDelete.isEmpty()) { return; } } Intent intent = new Intent(ApplicationLoader.applicationContext, LaunchActivity.class); intent.setAction("new_dialog"); ArrayList arrayList = new ArrayList<>(); arrayList.add(new ShortcutInfoCompat.Builder(ApplicationLoader.applicationContext, "compose") .setShortLabel(LocaleController.getString("NewConversationShortcut", R.string.NewConversationShortcut)) .setLongLabel(LocaleController.getString("NewConversationShortcut", R.string.NewConversationShortcut)) .setIcon(IconCompat.createWithResource(ApplicationLoader.applicationContext, R.drawable.shortcut_compose)) .setIntent(intent) .build()); if (shortcutsToUpdate.contains("compose")) { ShortcutManagerCompat.updateShortcuts(ApplicationLoader.applicationContext, arrayList); } else { ShortcutManagerCompat.addDynamicShortcuts(ApplicationLoader.applicationContext, arrayList); } arrayList.clear(); if (!shortcutsToDelete.isEmpty()) { ShortcutManagerCompat.removeDynamicShortcuts(ApplicationLoader.applicationContext, shortcutsToDelete); } HashSet category = new HashSet<>(1); category.add(SHORTCUT_CATEGORY); for (int a = 0; a < hintsFinal.size(); a++) { Intent shortcutIntent = new Intent(ApplicationLoader.applicationContext, OpenChatReceiver.class); TLRPC.TL_topPeer hint = hintsFinal.get(a); TLRPC.User user = null; TLRPC.Chat chat = null; long did; if (hint.peer.user_id != 0) { shortcutIntent.putExtra("userId", hint.peer.user_id); user = getMessagesController().getUser(hint.peer.user_id); did = hint.peer.user_id; } else { int chat_id = hint.peer.chat_id; if (chat_id == 0) { chat_id = hint.peer.channel_id; } chat = getMessagesController().getChat(chat_id); shortcutIntent.putExtra("chatId", chat_id); did = -chat_id; } if ((user == null || UserObject.isDeleted(user)) && chat == null) { continue; } String name; TLRPC.FileLocation photo = null; if (user != null) { name = ContactsController.formatName(user.first_name, user.last_name); if (user.photo != null) { photo = user.photo.photo_small; } } else { name = chat.title; if (chat.photo != null) { photo = chat.photo.photo_small; } } shortcutIntent.putExtra("currentAccount", currentAccount); shortcutIntent.setAction("com.tmessages.openchat" + did); shortcutIntent.putExtra("dialogId", did); shortcutIntent.putExtra("hash", SharedConfig.directShareHash); shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); Bitmap bitmap = null; if (photo != null) { try { File path = FileLoader.getPathToAttach(photo, true); bitmap = BitmapFactory.decodeFile(path.toString()); if (bitmap != null) { int size = AndroidUtilities.dp(48); Bitmap result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(result); if (roundPaint == null) { roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); bitmapRect = new RectF(); erasePaint = new Paint(Paint.ANTI_ALIAS_FLAG); erasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); roundPath = new Path(); roundPath.addCircle(size / 2, size / 2, size / 2 - AndroidUtilities.dp(2), Path.Direction.CW); roundPath.toggleInverseFillType(); } bitmapRect.set(AndroidUtilities.dp(2), AndroidUtilities.dp(2), AndroidUtilities.dp(46), AndroidUtilities.dp(46)); canvas.drawBitmap(bitmap, null, bitmapRect, roundPaint); canvas.drawPath(roundPath, erasePaint); try { canvas.setBitmap(null); } catch (Exception ignore) { } bitmap = result; } } catch (Throwable e) { FileLog.e(e); } } String id = "did3_" + did; if (TextUtils.isEmpty(name)) { name = " "; } ShortcutInfoCompat.Builder builder = new ShortcutInfoCompat.Builder(ApplicationLoader.applicationContext, id) .setShortLabel(name) .setLongLabel(name) .setIntent(shortcutIntent); if (SharedConfig.directShare) { builder.setCategories(category); } if (bitmap != null) { builder.setIcon(IconCompat.createWithBitmap(bitmap)); } else { builder.setIcon(IconCompat.createWithResource(ApplicationLoader.applicationContext, R.drawable.shortcut_user)); } arrayList.add(builder.build()); if (shortcutsToUpdate.contains(id)) { ShortcutManagerCompat.updateShortcuts(ApplicationLoader.applicationContext, arrayList); } else { ShortcutManagerCompat.addDynamicShortcuts(ApplicationLoader.applicationContext, arrayList); } arrayList.clear(); } } catch (Throwable ignore) { } }); } public void loadHints(boolean cache) { if (loading || !getUserConfig().suggestContacts) { return; } if (cache) { if (loaded) { return; } loading = true; getMessagesStorage().getStorageQueue().postRunnable(() -> { final ArrayList hintsNew = new ArrayList<>(); final ArrayList inlineBotsNew = new ArrayList<>(); final ArrayList users = new ArrayList<>(); final ArrayList chats = new ArrayList<>(); int selfUserId = getUserConfig().getClientUserId(); try { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT did, type, rating FROM chat_hints WHERE 1 ORDER BY rating DESC"); while (cursor.next()) { int did = cursor.intValue(0); if (did == selfUserId) { continue; } int type = cursor.intValue(1); TLRPC.TL_topPeer peer = new TLRPC.TL_topPeer(); peer.rating = cursor.doubleValue(2); if (did > 0) { peer.peer = new TLRPC.TL_peerUser(); peer.peer.user_id = did; usersToLoad.add(did); } else { peer.peer = new TLRPC.TL_peerChat(); peer.peer.chat_id = -did; chatsToLoad.add(-did); } if (type == 0) { hintsNew.add(peer); } else if (type == 1) { inlineBotsNew.add(peer); } } cursor.dispose(); if (!usersToLoad.isEmpty()) { getMessagesStorage().getUsersInternal(TextUtils.join(",", usersToLoad), users); } if (!chatsToLoad.isEmpty()) { getMessagesStorage().getChatsInternal(TextUtils.join(",", chatsToLoad), chats); } AndroidUtilities.runOnUIThread(() -> { getMessagesController().putUsers(users, true); getMessagesController().putChats(chats, true); loading = false; loaded = true; hints = hintsNew; inlineBots = inlineBotsNew; buildShortcuts(); getNotificationCenter().postNotificationName(NotificationCenter.reloadHints); getNotificationCenter().postNotificationName(NotificationCenter.reloadInlineHints); if (Math.abs(getUserConfig().lastHintsSyncTime - (int) (System.currentTimeMillis() / 1000)) >= 24 * 60 * 60) { loadHints(false); } }); } catch (Exception e) { FileLog.e(e); } }); loaded = true; } else { loading = true; TLRPC.TL_contacts_getTopPeers req = new TLRPC.TL_contacts_getTopPeers(); req.hash = 0; req.bots_pm = false; req.correspondents = true; req.groups = false; req.channels = false; req.bots_inline = true; req.offset = 0; req.limit = 20; getConnectionsManager().sendRequest(req, (response, error) -> { if (response instanceof TLRPC.TL_contacts_topPeers) { AndroidUtilities.runOnUIThread(() -> { final TLRPC.TL_contacts_topPeers topPeers = (TLRPC.TL_contacts_topPeers) response; getMessagesController().putUsers(topPeers.users, false); getMessagesController().putChats(topPeers.chats, false); for (int a = 0; a < topPeers.categories.size(); a++) { TLRPC.TL_topPeerCategoryPeers category = topPeers.categories.get(a); if (category.category instanceof TLRPC.TL_topPeerCategoryBotsInline) { inlineBots = category.peers; getUserConfig().botRatingLoadTime = (int) (System.currentTimeMillis() / 1000); } else { hints = category.peers; int selfUserId = getUserConfig().getClientUserId(); for (int b = 0; b < hints.size(); b++) { TLRPC.TL_topPeer topPeer = hints.get(b); if (topPeer.peer.user_id == selfUserId) { hints.remove(b); break; } } getUserConfig().ratingLoadTime = (int) (System.currentTimeMillis() / 1000); } } getUserConfig().saveConfig(false); buildShortcuts(); getNotificationCenter().postNotificationName(NotificationCenter.reloadHints); getNotificationCenter().postNotificationName(NotificationCenter.reloadInlineHints); getMessagesStorage().getStorageQueue().postRunnable(() -> { try { getMessagesStorage().getDatabase().executeFast("DELETE FROM chat_hints WHERE 1").stepThis().dispose(); getMessagesStorage().getDatabase().beginTransaction(); getMessagesStorage().putUsersAndChats(topPeers.users, topPeers.chats, false, false); SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO chat_hints VALUES(?, ?, ?, ?)"); for (int a = 0; a < topPeers.categories.size(); a++) { int type; TLRPC.TL_topPeerCategoryPeers category = topPeers.categories.get(a); if (category.category instanceof TLRPC.TL_topPeerCategoryBotsInline) { type = 1; } else { type = 0; } for (int b = 0; b < category.peers.size(); b++) { TLRPC.TL_topPeer peer = category.peers.get(b); int did; if (peer.peer instanceof TLRPC.TL_peerUser) { did = peer.peer.user_id; } else if (peer.peer instanceof TLRPC.TL_peerChat) { did = -peer.peer.chat_id; } else { did = -peer.peer.channel_id; } state.requery(); state.bindInteger(1, did); state.bindInteger(2, type); state.bindDouble(3, peer.rating); state.bindInteger(4, 0); state.step(); } } state.dispose(); getMessagesStorage().getDatabase().commitTransaction(); AndroidUtilities.runOnUIThread(() -> { getUserConfig().suggestContacts = true; getUserConfig().lastHintsSyncTime = (int) (System.currentTimeMillis() / 1000); getUserConfig().saveConfig(false); }); } catch (Exception e) { FileLog.e(e); } }); }); } else if (response instanceof TLRPC.TL_contacts_topPeersDisabled) { AndroidUtilities.runOnUIThread(() -> { getUserConfig().suggestContacts = false; getUserConfig().lastHintsSyncTime = (int) (System.currentTimeMillis() / 1000); getUserConfig().saveConfig(false); clearTopPeers(); }); } }); } } public void clearTopPeers() { hints.clear(); inlineBots.clear(); getNotificationCenter().postNotificationName(NotificationCenter.reloadHints); getNotificationCenter().postNotificationName(NotificationCenter.reloadInlineHints); getMessagesStorage().getStorageQueue().postRunnable(() -> { try { getMessagesStorage().getDatabase().executeFast("DELETE FROM chat_hints WHERE 1").stepThis().dispose(); } catch (Exception ignore) { } }); buildShortcuts(); } public void increaseInlineRaiting(final int uid) { if (!getUserConfig().suggestContacts) { return; } int dt; if (getUserConfig().botRatingLoadTime != 0) { dt = Math.max(1, ((int) (System.currentTimeMillis() / 1000)) - getUserConfig().botRatingLoadTime); } else { dt = 60; } TLRPC.TL_topPeer peer = null; for (int a = 0; a < inlineBots.size(); a++) { TLRPC.TL_topPeer p = inlineBots.get(a); if (p.peer.user_id == uid) { peer = p; break; } } if (peer == null) { peer = new TLRPC.TL_topPeer(); peer.peer = new TLRPC.TL_peerUser(); peer.peer.user_id = uid; inlineBots.add(peer); } peer.rating += Math.exp(dt / getMessagesController().ratingDecay); Collections.sort(inlineBots, (lhs, rhs) -> { if (lhs.rating > rhs.rating) { return -1; } else if (lhs.rating < rhs.rating) { return 1; } return 0; }); if (inlineBots.size() > 20) { inlineBots.remove(inlineBots.size() - 1); } savePeer(uid, 1, peer.rating); getNotificationCenter().postNotificationName(NotificationCenter.reloadInlineHints); } public void removeInline(final int uid) { TLRPC.TL_topPeerCategoryPeers category = null; for (int a = 0; a < inlineBots.size(); a++) { if (inlineBots.get(a).peer.user_id == uid) { inlineBots.remove(a); TLRPC.TL_contacts_resetTopPeerRating req = new TLRPC.TL_contacts_resetTopPeerRating(); req.category = new TLRPC.TL_topPeerCategoryBotsInline(); req.peer = getMessagesController().getInputPeer(uid); getConnectionsManager().sendRequest(req, (response, error) -> { }); deletePeer(uid, 1); getNotificationCenter().postNotificationName(NotificationCenter.reloadInlineHints); return; } } } public void removePeer(final int uid) { for (int a = 0; a < hints.size(); a++) { if (hints.get(a).peer.user_id == uid) { hints.remove(a); getNotificationCenter().postNotificationName(NotificationCenter.reloadHints); TLRPC.TL_contacts_resetTopPeerRating req = new TLRPC.TL_contacts_resetTopPeerRating(); req.category = new TLRPC.TL_topPeerCategoryCorrespondents(); req.peer = getMessagesController().getInputPeer(uid); deletePeer(uid, 0); getConnectionsManager().sendRequest(req, (response, error) -> { }); return; } } } public void increasePeerRaiting(final long did) { if (!getUserConfig().suggestContacts) { return; } final int lower_id = (int) did; if (lower_id <= 0) { return; } //remove chats and bots for now final TLRPC.User user = lower_id > 0 ? getMessagesController().getUser(lower_id) : null; //final TLRPC.Chat chat = lower_id < 0 ? MessagesController.getInstance().getChat(-lower_id) : null; if (user == null || user.bot || user.self/*&& chat == null || ChatObject.isChannel(chat) && !chat.megagroup*/) { return; } getMessagesStorage().getStorageQueue().postRunnable(() -> { double dt = 0; try { int lastTime = 0; int lastMid = 0; SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT MAX(mid), MAX(date) FROM messages WHERE uid = %d AND out = 1", did)); if (cursor.next()) { lastMid = cursor.intValue(0); lastTime = cursor.intValue(1); } cursor.dispose(); if (lastMid > 0 && getUserConfig().ratingLoadTime != 0) { dt = (lastTime - getUserConfig().ratingLoadTime); } } catch (Exception e) { FileLog.e(e); } final double dtFinal = dt; AndroidUtilities.runOnUIThread(() -> { TLRPC.TL_topPeer peer = null; for (int a = 0; a < hints.size(); a++) { TLRPC.TL_topPeer p = hints.get(a); if (lower_id < 0 && (p.peer.chat_id == -lower_id || p.peer.channel_id == -lower_id) || lower_id > 0 && p.peer.user_id == lower_id) { peer = p; break; } } if (peer == null) { peer = new TLRPC.TL_topPeer(); if (lower_id > 0) { peer.peer = new TLRPC.TL_peerUser(); peer.peer.user_id = lower_id; } else { peer.peer = new TLRPC.TL_peerChat(); peer.peer.chat_id = -lower_id; } hints.add(peer); } peer.rating += Math.exp(dtFinal / getMessagesController().ratingDecay); Collections.sort(hints, (lhs, rhs) -> { if (lhs.rating > rhs.rating) { return -1; } else if (lhs.rating < rhs.rating) { return 1; } return 0; }); savePeer((int) did, 0, peer.rating); getNotificationCenter().postNotificationName(NotificationCenter.reloadHints); }); }); } private void savePeer(final int did, final int type, final double rating) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO chat_hints VALUES(?, ?, ?, ?)"); state.requery(); state.bindInteger(1, did); state.bindInteger(2, type); state.bindDouble(3, rating); state.bindInteger(4, (int) System.currentTimeMillis() / 1000); state.step(); state.dispose(); } catch (Exception e) { FileLog.e(e); } }); } private void deletePeer(final int did, final int type) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { getMessagesStorage().getDatabase().executeFast(String.format(Locale.US, "DELETE FROM chat_hints WHERE did = %d AND type = %d", did, type)).stepThis().dispose(); } catch (Exception e) { FileLog.e(e); } }); } private Intent createIntrnalShortcutIntent(long did) { Intent shortcutIntent = new Intent(ApplicationLoader.applicationContext, OpenChatReceiver.class); int lower_id = (int) did; int high_id = (int) (did >> 32); if (lower_id == 0) { shortcutIntent.putExtra("encId", high_id); TLRPC.EncryptedChat encryptedChat = getMessagesController().getEncryptedChat(high_id); if (encryptedChat == null) { return null; } } else if (lower_id > 0) { shortcutIntent.putExtra("userId", lower_id); } else if (lower_id < 0) { shortcutIntent.putExtra("chatId", -lower_id); } else { return null; } shortcutIntent.putExtra("currentAccount", currentAccount); shortcutIntent.setAction("com.tmessages.openchat" + did); shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); return shortcutIntent; } public void installShortcut(long did) { try { Intent shortcutIntent = createIntrnalShortcutIntent(did); int lower_id = (int) did; int high_id = (int) (did >> 32); TLRPC.User user = null; TLRPC.Chat chat = null; if (lower_id == 0) { TLRPC.EncryptedChat encryptedChat = getMessagesController().getEncryptedChat(high_id); if (encryptedChat == null) { return; } user = getMessagesController().getUser(encryptedChat.user_id); } else if (lower_id > 0) { user = getMessagesController().getUser(lower_id); } else if (lower_id < 0) { chat = getMessagesController().getChat(-lower_id); } else { return; } if (user == null && chat == null) { return; } String name; TLRPC.FileLocation photo = null; boolean overrideAvatar = false; if (user != null) { if (UserObject.isReplyUser(user)) { name = LocaleController.getString("RepliesTitle", R.string.RepliesTitle); overrideAvatar = true; } else if (UserObject.isUserSelf(user)) { name = LocaleController.getString("SavedMessages", R.string.SavedMessages); overrideAvatar = true; } else { name = ContactsController.formatName(user.first_name, user.last_name); if (user.photo != null) { photo = user.photo.photo_small; } } } else { name = chat.title; if (chat.photo != null) { photo = chat.photo.photo_small; } } Bitmap bitmap = null; if (overrideAvatar || photo != null) { try { if (!overrideAvatar) { File path = FileLoader.getPathToAttach(photo, true); bitmap = BitmapFactory.decodeFile(path.toString()); } if (overrideAvatar || bitmap != null) { int size = AndroidUtilities.dp(58); Bitmap result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); result.eraseColor(Color.TRANSPARENT); Canvas canvas = new Canvas(result); if (overrideAvatar) { AvatarDrawable avatarDrawable = new AvatarDrawable(user); if (UserObject.isReplyUser(user)) { avatarDrawable.setAvatarType(AvatarDrawable.AVATAR_TYPE_REPLIES); } else { avatarDrawable.setAvatarType(AvatarDrawable.AVATAR_TYPE_SAVED); } avatarDrawable.setBounds(0, 0, size, size); avatarDrawable.draw(canvas); } else { BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); if (roundPaint == null) { roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); bitmapRect = new RectF(); } float scale = size / (float) bitmap.getWidth(); canvas.save(); canvas.scale(scale, scale); roundPaint.setShader(shader); bitmapRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); canvas.drawRoundRect(bitmapRect, bitmap.getWidth(), bitmap.getHeight(), roundPaint); canvas.restore(); } Drawable drawable = ApplicationLoader.applicationContext.getResources().getDrawable(R.drawable.book_logo); int w = AndroidUtilities.dp(15); int left = size - w - AndroidUtilities.dp(2); int top = size - w - AndroidUtilities.dp(2); drawable.setBounds(left, top, left + w, top + w); drawable.draw(canvas); try { canvas.setBitmap(null); } catch (Exception e) { //don't promt, this will crash on 2.x } bitmap = result; } } catch (Throwable e) { FileLog.e(e); } } if (Build.VERSION.SDK_INT >= 26) { ShortcutInfoCompat.Builder pinShortcutInfo = new ShortcutInfoCompat.Builder(ApplicationLoader.applicationContext, "sdid_" + did) .setShortLabel(name) .setIntent(shortcutIntent); if (bitmap != null) { pinShortcutInfo.setIcon(IconCompat.createWithBitmap(bitmap)); } else { if (user != null) { if (user.bot) { pinShortcutInfo.setIcon(IconCompat.createWithResource(ApplicationLoader.applicationContext, R.drawable.book_bot)); } else { pinShortcutInfo.setIcon(IconCompat.createWithResource(ApplicationLoader.applicationContext, R.drawable.book_user)); } } else if (chat != null) { if (ChatObject.isChannel(chat) && !chat.megagroup) { pinShortcutInfo.setIcon(IconCompat.createWithResource(ApplicationLoader.applicationContext, R.drawable.book_channel)); } else { pinShortcutInfo.setIcon(IconCompat.createWithResource(ApplicationLoader.applicationContext, R.drawable.book_group)); } } } ShortcutManagerCompat.requestPinShortcut(ApplicationLoader.applicationContext, pinShortcutInfo.build(), null); } else { Intent addIntent = new Intent(); if (bitmap != null) { addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap); } else { if (user != null) { if (user.bot) { addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_bot)); } else { addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_user)); } } else if (chat != null) { if (ChatObject.isChannel(chat) && !chat.megagroup) { addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_channel)); } else { addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(ApplicationLoader.applicationContext, R.drawable.book_group)); } } } addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); addIntent.putExtra("duplicate", false); addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); ApplicationLoader.applicationContext.sendBroadcast(addIntent); } } catch (Exception e) { FileLog.e(e); } } public void uninstallShortcut(long did) { try { if (Build.VERSION.SDK_INT >= 26) { ArrayList arrayList = new ArrayList<>(); arrayList.add("sdid_" + did); arrayList.add("ndid_" + did); ShortcutManagerCompat.removeDynamicShortcuts(ApplicationLoader.applicationContext, arrayList); if (Build.VERSION.SDK_INT >= 30) { ShortcutManager shortcutManager = ApplicationLoader.applicationContext.getSystemService(ShortcutManager.class); shortcutManager.removeLongLivedShortcuts(arrayList); } } else { int lower_id = (int) did; int high_id = (int) (did >> 32); TLRPC.User user = null; TLRPC.Chat chat = null; if (lower_id == 0) { TLRPC.EncryptedChat encryptedChat = getMessagesController().getEncryptedChat(high_id); if (encryptedChat == null) { return; } user = getMessagesController().getUser(encryptedChat.user_id); } else if (lower_id > 0) { user = getMessagesController().getUser(lower_id); } else if (lower_id < 0) { chat = getMessagesController().getChat(-lower_id); } else { return; } if (user == null && chat == null) { return; } String name; if (user != null) { name = ContactsController.formatName(user.first_name, user.last_name); } else { name = chat.title; } Intent addIntent = new Intent(); addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, createIntrnalShortcutIntent(did)); addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); addIntent.putExtra("duplicate", false); addIntent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT"); ApplicationLoader.applicationContext.sendBroadcast(addIntent); } } catch (Exception e) { FileLog.e(e); } } //---------------- SEARCH END ---------------- private static Comparator entityComparator = (entity1, entity2) -> { if (entity1.offset > entity2.offset) { return 1; } else if (entity1.offset < entity2.offset) { return -1; } return 0; }; private LongSparseArray loadingPinnedMessages = new LongSparseArray<>(); public void loadPinnedMessages(long dialogId, int maxId, int fallback) { if (loadingPinnedMessages.indexOfKey(dialogId) >= 0) { return; } loadingPinnedMessages.put(dialogId, true); TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); req.peer = getMessagesController().getInputPeer((int) dialogId); req.limit = 40; req.offset_id = maxId; req.q = ""; req.filter = new TLRPC.TL_inputMessagesFilterPinned(); getConnectionsManager().sendRequest(req, (response, error) -> { ArrayList ids = new ArrayList<>(); HashMap messages = new HashMap<>(); int totalCount = 0; boolean endReached; if (response instanceof TLRPC.messages_Messages) { TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; final SparseArray usersDict = new SparseArray<>(); for (int a = 0; a < res.users.size(); a++) { TLRPC.User user = res.users.get(a); usersDict.put(user.id, user); } final SparseArray chatsDict = new SparseArray<>(); for (int a = 0; a < res.chats.size(); a++) { TLRPC.Chat chat = res.chats.get(a); chatsDict.put(chat.id, chat); } getMessagesStorage().putUsersAndChats(res.users, res.chats, true, true); getMessagesController().putUsers(res.users, false); getMessagesController().putChats(res.chats, false); for (int a = 0, N = res.messages.size(); a < N; a++) { TLRPC.Message message = res.messages.get(a); if (message instanceof TLRPC.TL_messageService || message instanceof TLRPC.TL_messageEmpty) { continue; } ids.add(message.id); messages.put(message.id, new MessageObject(currentAccount, message, usersDict, chatsDict, false, false)); } if (fallback != 0 && ids.isEmpty()) { ids.add(fallback); } endReached = res.messages.size() < req.limit; totalCount = Math.max(res.count, res.messages.size()); } else { if (fallback != 0) { ids.add(fallback); totalCount = 1; } endReached = false; } getMessagesStorage().updatePinnedMessages(dialogId, ids, true, totalCount, maxId, endReached, messages); AndroidUtilities.runOnUIThread(() -> loadingPinnedMessages.remove(dialogId)); }); } public ArrayList loadPinnedMessages(long dialogId, int channelId, ArrayList mids, boolean useQueue) { if (useQueue) { getMessagesStorage().getStorageQueue().postRunnable(() -> loadPinnedMessageInternal(dialogId, channelId, mids, false)); } else { return loadPinnedMessageInternal(dialogId, channelId, mids, true); } return null; } private ArrayList loadPinnedMessageInternal(long dialogId, int channelId, ArrayList mids, boolean returnValue) { try { ArrayList midsCopy = new ArrayList<>(mids); CharSequence longIds; if (channelId != 0) { StringBuilder builder = new StringBuilder(); for (int a = 0, N = mids.size(); a < N; a++) { long messageId = ((long) mids.get(a)) | ((long) channelId) << 32; if (builder.length() != 0) { builder.append(","); } builder.append(messageId); } longIds = builder; } else { longIds = TextUtils.join(",", mids); } ArrayList results = new ArrayList<>(); final ArrayList users = new ArrayList<>(); final ArrayList chats = new ArrayList<>(); ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid, date FROM messages WHERE mid IN (%s)", longIds)); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message result = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); result.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (result.action instanceof TLRPC.TL_messageActionHistoryClear) { result = null; } else { result.id = cursor.intValue(1); result.date = cursor.intValue(2); result.dialog_id = dialogId; MessagesStorage.addUsersAndChatsFromMessage(result, usersToLoad, chatsToLoad); results.add(result); } midsCopy.remove((Integer) result.id); } } cursor.dispose(); if (!midsCopy.isEmpty()) { cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data FROM chat_pinned_v2 WHERE uid = %d AND mid IN (%s)", dialogId, TextUtils.join(",", midsCopy))); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message result = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); result.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); if (result.action instanceof TLRPC.TL_messageActionHistoryClear) { result = null; } else { result.dialog_id = dialogId; MessagesStorage.addUsersAndChatsFromMessage(result, usersToLoad, chatsToLoad); results.add(result); } midsCopy.remove((Integer) result.id); } } cursor.dispose(); } if (!midsCopy.isEmpty()) { if (channelId != 0) { final TLRPC.TL_channels_getMessages req = new TLRPC.TL_channels_getMessages(); req.channel = getMessagesController().getInputChannel(channelId); req.id = midsCopy; getConnectionsManager().sendRequest(req, (response, error) -> { boolean ok = false; if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; removeEmptyMessages(messagesRes.messages); if (!messagesRes.messages.isEmpty()) { TLRPC.Chat chat = getMessagesController().getChat(channelId); ImageLoader.saveMessagesThumbs(messagesRes.messages); broadcastPinnedMessage(messagesRes.messages, messagesRes.users, messagesRes.chats, false, false); getMessagesStorage().putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); savePinnedMessages(dialogId, messagesRes.messages); ok = true; } } if (!ok) { getMessagesStorage().updatePinnedMessages(dialogId, req.id, false, -1, 0, false, null); } }); } else { final TLRPC.TL_messages_getMessages req = new TLRPC.TL_messages_getMessages(); req.id = midsCopy; getConnectionsManager().sendRequest(req, (response, error) -> { boolean ok = false; if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; removeEmptyMessages(messagesRes.messages); if (!messagesRes.messages.isEmpty()) { ImageLoader.saveMessagesThumbs(messagesRes.messages); broadcastPinnedMessage(messagesRes.messages, messagesRes.users, messagesRes.chats, false, false); getMessagesStorage().putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); savePinnedMessages(dialogId, messagesRes.messages); ok = true; } } if (!ok) { getMessagesStorage().updatePinnedMessages(dialogId, req.id, false, -1, 0, false, null); } }); } } if (!results.isEmpty()) { if (!usersToLoad.isEmpty()) { getMessagesStorage().getUsersInternal(TextUtils.join(",", usersToLoad), users); } if (!chatsToLoad.isEmpty()) { getMessagesStorage().getChatsInternal(TextUtils.join(",", chatsToLoad), chats); } if (returnValue) { return broadcastPinnedMessage(results, users, chats, true, true); } else { broadcastPinnedMessage(results, users, chats, true, false); } } } catch (Exception e) { FileLog.e(e); } return null; } private void savePinnedMessages(long dialogId, ArrayList arrayList) { if (arrayList.isEmpty()) { return; } getMessagesStorage().getStorageQueue().postRunnable(() -> { try { getMessagesStorage().getDatabase().beginTransaction(); //SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("UPDATE chat_pinned_v2 SET data = ? WHERE uid = ? AND mid = ?"); SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO chat_pinned_v2 VALUES(?, ?, ?)"); for (int a = 0, N = arrayList.size(); a < N; a++) { TLRPC.Message message = arrayList.get(a); NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); state.requery(); state.bindLong(1, dialogId); state.bindInteger(2, message.id); state.bindByteBuffer(3, data); state.step(); data.reuse(); } state.dispose(); getMessagesStorage().getDatabase().commitTransaction(); } catch (Exception e) { FileLog.e(e); } }); } private ArrayList broadcastPinnedMessage(final ArrayList results, final ArrayList users, final ArrayList chats, final boolean isCache, boolean returnValue) { if (results.isEmpty()) { return null; } final SparseArray usersDict = new SparseArray<>(); for (int a = 0; a < users.size(); a++) { TLRPC.User user = users.get(a); usersDict.put(user.id, user); } final SparseArray chatsDict = new SparseArray<>(); for (int a = 0; a < chats.size(); a++) { TLRPC.Chat chat = chats.get(a); chatsDict.put(chat.id, chat); } ArrayList messageObjects = new ArrayList<>(); if (returnValue) { AndroidUtilities.runOnUIThread(() -> { getMessagesController().putUsers(users, isCache); getMessagesController().putChats(chats, isCache); }); int checkedCount = 0; for (int a = 0, N = results.size(); a < N; a++) { TLRPC.Message message = results.get(a); if (message.media instanceof TLRPC.TL_messageMediaDocument || message.media instanceof TLRPC.TL_messageMediaPhoto) { checkedCount++; } messageObjects.add(new MessageObject(currentAccount, message, usersDict, chatsDict, false, checkedCount < 30)); } return messageObjects; } else { AndroidUtilities.runOnUIThread(() -> { getMessagesController().putUsers(users, isCache); getMessagesController().putChats(chats, isCache); int checkedCount = 0; for (int a = 0, N = results.size(); a < N; a++) { TLRPC.Message message = results.get(a); if (message.media instanceof TLRPC.TL_messageMediaDocument || message.media instanceof TLRPC.TL_messageMediaPhoto) { checkedCount++; } messageObjects.add(new MessageObject(currentAccount, message, usersDict, chatsDict, false, checkedCount < 30)); } AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.didLoadPinnedMessages, messageObjects.get(0).getDialogId(), null, true, messageObjects, null, 0, -1, false)); }); } return null; } private static void removeEmptyMessages(ArrayList messages) { for (int a = 0; a < messages.size(); a++) { TLRPC.Message message = messages.get(a); if (message == null || message instanceof TLRPC.TL_messageEmpty || message.action instanceof TLRPC.TL_messageActionHistoryClear) { messages.remove(a); a--; } } } public void loadReplyMessagesForMessages(final ArrayList messages, final long dialogId, boolean scheduled, Runnable callback) { if ((int) dialogId == 0) { final ArrayList replyMessages = new ArrayList<>(); final LongSparseArray> replyMessageRandomOwners = new LongSparseArray<>(); for (int a = 0; a < messages.size(); a++) { MessageObject messageObject = messages.get(a); if (messageObject == null) { continue; } if (messageObject.isReply() && messageObject.replyMessageObject == null) { long id = messageObject.messageOwner.reply_to.reply_to_random_id; ArrayList messageObjects = replyMessageRandomOwners.get(id); if (messageObjects == null) { messageObjects = new ArrayList<>(); replyMessageRandomOwners.put(id, messageObjects); } messageObjects.add(messageObject); if (!replyMessages.contains(id)) { replyMessages.add(id); } } } if (replyMessages.isEmpty()) { if (callback != null) { callback.run(); } return; } getMessagesStorage().getStorageQueue().postRunnable(() -> { try { ArrayList loadedMessages = new ArrayList<>(); SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, m.date, r.random_id FROM randoms as r INNER JOIN messages as m ON r.mid = m.mid WHERE r.random_id IN(%s)", TextUtils.join(",", replyMessages))); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); message.id = cursor.intValue(1); message.date = cursor.intValue(2); message.dialog_id = dialogId; long value = cursor.longValue(3); ArrayList arrayList = replyMessageRandomOwners.get(value); replyMessageRandomOwners.remove(value); if (arrayList != null) { MessageObject messageObject = new MessageObject(currentAccount, message, false, false); loadedMessages.add(messageObject); for (int b = 0; b < arrayList.size(); b++) { MessageObject object = arrayList.get(b); object.replyMessageObject = messageObject; object.messageOwner.reply_to = new TLRPC.TL_messageReplyHeader(); object.messageOwner.reply_to.reply_to_msg_id = messageObject.getId(); } } } } cursor.dispose(); if (replyMessageRandomOwners.size() != 0) { for (int b = 0; b < replyMessageRandomOwners.size(); b++) { ArrayList arrayList = replyMessageRandomOwners.valueAt(b); for (int a = 0; a < arrayList.size(); a++) { TLRPC.Message message = arrayList.get(a).messageOwner; if (message.reply_to != null) { message.reply_to.reply_to_random_id = 0; } } } } AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.replyMessagesDidLoad, dialogId, loadedMessages)); if (callback != null) { callback.run(); } } catch (Exception e) { FileLog.e(e); } }); } else { final HashMap> replyMessages = new HashMap<>(); final LongSparseArray> replyMessageOwners = new LongSparseArray<>(); final StringBuilder stringBuilder = new StringBuilder(); for (int a = 0; a < messages.size(); a++) { MessageObject messageObject = messages.get(a); if (messageObject == null) { continue; } if (messageObject.getId() > 0 && messageObject.isReply() && messageObject.replyMessageObject == null) { int id = messageObject.messageOwner.reply_to.reply_to_msg_id; long messageId = id; int channelId = 0; if (messageObject.messageOwner.reply_to.reply_to_peer_id != null) { if (messageObject.messageOwner.reply_to.reply_to_peer_id.channel_id != 0) { messageId |= ((long) messageObject.messageOwner.reply_to.reply_to_peer_id.channel_id) << 32; channelId = messageObject.messageOwner.reply_to.reply_to_peer_id.channel_id; } } else if (messageObject.messageOwner.peer_id.channel_id != 0) { messageId |= ((long) messageObject.messageOwner.peer_id.channel_id) << 32; channelId = messageObject.messageOwner.peer_id.channel_id; } if (stringBuilder.length() > 0) { stringBuilder.append(','); } stringBuilder.append(messageId); ArrayList messageObjects = replyMessageOwners.get(messageId); if (messageObjects == null) { messageObjects = new ArrayList<>(); replyMessageOwners.put(messageId, messageObjects); } messageObjects.add(messageObject); ArrayList mids = replyMessages.get(channelId); if (mids == null) { mids = new ArrayList<>(); replyMessages.put(channelId, mids); } if (!mids.contains(id)) { mids.add(id); } } } if (replyMessages.isEmpty()) { if (callback != null) { callback.run(); } return; } getMessagesStorage().getStorageQueue().postRunnable(() -> { try { final ArrayList result = new ArrayList<>(); final ArrayList users = new ArrayList<>(); final ArrayList chats = new ArrayList<>(); ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid, date FROM messages WHERE mid IN(%s)", stringBuilder.toString())); while (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); message.id = cursor.intValue(1); message.date = cursor.intValue(2); message.dialog_id = dialogId; MessagesStorage.addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); result.add(message); Integer channelId = message.peer_id != null ? message.peer_id.channel_id : 0; ArrayList mids = replyMessages.get(channelId); if (mids != null) { mids.remove((Integer) message.id); if (mids.isEmpty()) { replyMessages.remove(channelId); } } } } cursor.dispose(); if (!usersToLoad.isEmpty()) { getMessagesStorage().getUsersInternal(TextUtils.join(",", usersToLoad), users); } if (!chatsToLoad.isEmpty()) { getMessagesStorage().getChatsInternal(TextUtils.join(",", chatsToLoad), chats); } broadcastReplyMessages(result, replyMessageOwners, users, chats, dialogId, true); if (!replyMessages.isEmpty()) { for (HashMap.Entry> entry : replyMessages.entrySet()) { int channelId = entry.getKey(); if (channelId != 0) { final TLRPC.TL_channels_getMessages req = new TLRPC.TL_channels_getMessages(); req.channel = getMessagesController().getInputChannel(channelId); req.id = entry.getValue(); getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; MessageObject.fixMessagePeer(messagesRes.messages, channelId); ImageLoader.saveMessagesThumbs(messagesRes.messages); broadcastReplyMessages(messagesRes.messages, replyMessageOwners, messagesRes.users, messagesRes.chats, dialogId, false); getMessagesStorage().putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); saveReplyMessages(replyMessageOwners, messagesRes.messages, scheduled); } if (callback != null) { AndroidUtilities.runOnUIThread(callback); } }); } else { TLRPC.TL_messages_getMessages req = new TLRPC.TL_messages_getMessages(); req.id = entry.getValue(); getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; ImageLoader.saveMessagesThumbs(messagesRes.messages); broadcastReplyMessages(messagesRes.messages, replyMessageOwners, messagesRes.users, messagesRes.chats, dialogId, false); getMessagesStorage().putUsersAndChats(messagesRes.users, messagesRes.chats, true, true); saveReplyMessages(replyMessageOwners, messagesRes.messages, scheduled); } if (callback != null) { AndroidUtilities.runOnUIThread(callback); } }); } } } else { if (callback != null) { AndroidUtilities.runOnUIThread(callback); } } } catch (Exception e) { FileLog.e(e); } }); } } private void saveReplyMessages(final LongSparseArray> replyMessageOwners, final ArrayList result, boolean scheduled) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { getMessagesStorage().getDatabase().beginTransaction(); SQLitePreparedStatement state; if (scheduled) { state = getMessagesStorage().getDatabase().executeFast("UPDATE scheduled_messages SET replydata = ? WHERE mid = ?"); } else { state = getMessagesStorage().getDatabase().executeFast("UPDATE messages SET replydata = ? WHERE mid = ?"); } for (int a = 0; a < result.size(); a++) { TLRPC.Message message = result.get(a); ArrayList messageObjects = replyMessageOwners.get(MessageObject.getIdWithChannel(message)); if (messageObjects != null) { NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); for (int b = 0; b < messageObjects.size(); b++) { MessageObject messageObject = messageObjects.get(b); state.requery(); long messageId = messageObject.getId(); if (messageObject.messageOwner.peer_id.channel_id != 0) { messageId |= ((long) messageObject.messageOwner.peer_id.channel_id) << 32; } state.bindByteBuffer(1, data); state.bindLong(2, messageId); state.step(); } data.reuse(); } } state.dispose(); getMessagesStorage().getDatabase().commitTransaction(); } catch (Exception e) { FileLog.e(e); } }); } private void broadcastReplyMessages(ArrayList result, LongSparseArray> replyMessageOwners, ArrayList users, ArrayList chats, long dialog_id, boolean isCache) { final SparseArray usersDict = new SparseArray<>(); for (int a = 0; a < users.size(); a++) { TLRPC.User user = users.get(a); usersDict.put(user.id, user); } final SparseArray chatsDict = new SparseArray<>(); for (int a = 0; a < chats.size(); a++) { TLRPC.Chat chat = chats.get(a); chatsDict.put(chat.id, chat); } final ArrayList messageObjects = new ArrayList<>(); for (int a = 0, N = result.size(); a < N; a++) { messageObjects.add(new MessageObject(currentAccount, result.get(a), usersDict, chatsDict, false, false)); } AndroidUtilities.runOnUIThread(() -> { getMessagesController().putUsers(users, isCache); getMessagesController().putChats(chats, isCache); boolean changed = false; for (int a = 0, N = messageObjects.size(); a < N; a++) { MessageObject messageObject = messageObjects.get(a); ArrayList arrayList = replyMessageOwners.get(messageObject.getIdWithChannel()); if (arrayList != null) { for (int b = 0; b < arrayList.size(); b++) { MessageObject m = arrayList.get(b); m.replyMessageObject = messageObject; if (m.messageOwner.action instanceof TLRPC.TL_messageActionPinMessage) { m.generatePinMessageText(null, null); } else if (m.messageOwner.action instanceof TLRPC.TL_messageActionGameScore) { m.generateGameMessageText(null); } else if (m.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { m.generatePaymentSentMessageText(null); } } changed = true; } } if (changed) { getNotificationCenter().postNotificationName(NotificationCenter.replyMessagesDidLoad, dialog_id, messageObjects); } }); } public static void sortEntities(ArrayList entities) { Collections.sort(entities, entityComparator); } private static boolean checkInclusion(int index, ArrayList entities, boolean end) { if (entities == null || entities.isEmpty()) { return false; } int count = entities.size(); for (int a = 0; a < count; a++) { TLRPC.MessageEntity entity = entities.get(a); if ((end ? entity.offset < index : entity.offset <= index) && entity.offset + entity.length > index) { return true; } } return false; } private static boolean checkIntersection(int start, int end, ArrayList entities) { if (entities == null || entities.isEmpty()) { return false; } int count = entities.size(); for (int a = 0; a < count; a++) { TLRPC.MessageEntity entity = entities.get(a); if (entity.offset > start && entity.offset + entity.length <= end) { return true; } } return false; } private static void removeOffsetAfter(int start, int countToRemove, ArrayList entities) { int count = entities.size(); for (int a = 0; a < count; a++) { TLRPC.MessageEntity entity = entities.get(a); if (entity.offset > start) { entity.offset -= countToRemove; } } } public CharSequence substring(CharSequence source, int start, int end) { if (source instanceof SpannableStringBuilder) { return source.subSequence(start, end); } else if (source instanceof SpannedString) { return source.subSequence(start, end); } else { return TextUtils.substring(source, start, end); } } private static CharacterStyle createNewSpan(CharacterStyle baseSpan, TextStyleSpan.TextStyleRun textStyleRun, TextStyleSpan.TextStyleRun newStyleRun, boolean allowIntersection) { TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(textStyleRun); if (newStyleRun != null) { if (allowIntersection) { run.merge(newStyleRun); } else { run.replace(newStyleRun); } } if (baseSpan instanceof TextStyleSpan) { return new TextStyleSpan(run); } else if (baseSpan instanceof URLSpanReplacement) { URLSpanReplacement span = (URLSpanReplacement) baseSpan; return new URLSpanReplacement(span.getURL(), run); } return null; } public static void addStyleToText(TextStyleSpan span, int start, int end, Spannable editable, boolean allowIntersection) { try { CharacterStyle[] spans = editable.getSpans(start, end, CharacterStyle.class); if (spans != null && spans.length > 0) { for (int a = 0; a < spans.length; a++) { CharacterStyle oldSpan = spans[a]; TextStyleSpan.TextStyleRun textStyleRun; TextStyleSpan.TextStyleRun newStyleRun = span != null ? span.getTextStyleRun() : new TextStyleSpan.TextStyleRun(); if (oldSpan instanceof TextStyleSpan) { TextStyleSpan textStyleSpan = (TextStyleSpan) oldSpan; textStyleRun = textStyleSpan.getTextStyleRun(); } else if (oldSpan instanceof URLSpanReplacement) { URLSpanReplacement urlSpanReplacement = (URLSpanReplacement) oldSpan; textStyleRun = urlSpanReplacement.getTextStyleRun(); if (textStyleRun == null) { textStyleRun = new TextStyleSpan.TextStyleRun(); } } else { continue; } if (textStyleRun == null) { continue; } int spanStart = editable.getSpanStart(oldSpan); int spanEnd = editable.getSpanEnd(oldSpan); editable.removeSpan(oldSpan); if (spanStart > start && end > spanEnd) { editable.setSpan(createNewSpan(oldSpan, textStyleRun, newStyleRun, allowIntersection), spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); if (span != null) { editable.setSpan(new TextStyleSpan(new TextStyleSpan.TextStyleRun(newStyleRun)), spanEnd, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } end = spanStart; } else { int startTemp = start; if (spanStart <= start) { if (spanStart != start) { editable.setSpan(createNewSpan(oldSpan, textStyleRun, null, allowIntersection), spanStart, start, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } if (spanEnd > start) { if (span != null) { editable.setSpan(createNewSpan(oldSpan, textStyleRun, newStyleRun, allowIntersection), start, Math.min(spanEnd, end), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } start = spanEnd; } } if (spanEnd >= end) { if (spanEnd != end) { editable.setSpan(createNewSpan(oldSpan, textStyleRun, null, allowIntersection), end, spanEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } if (end > spanStart && spanEnd <= startTemp) { if (span != null) { editable.setSpan(createNewSpan(oldSpan, textStyleRun, newStyleRun, allowIntersection), spanStart, Math.min(spanEnd, end), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } end = spanStart; } } } } } if (span != null && start < end) { editable.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } catch (Exception e) { FileLog.e(e); } } public static ArrayList getTextStyleRuns(ArrayList entities, CharSequence text) { ArrayList runs = new ArrayList<>(); ArrayList entitiesCopy = new ArrayList<>(entities); Collections.sort(entitiesCopy, (o1, o2) -> { if (o1.offset > o2.offset) { return 1; } else if (o1.offset < o2.offset) { return -1; } return 0; }); for (int a = 0, N = entitiesCopy.size(); a < N; a++) { TLRPC.MessageEntity entity = entitiesCopy.get(a); if (entity.length <= 0 || entity.offset < 0 || entity.offset >= text.length()) { continue; } else if (entity.offset + entity.length > text.length()) { entity.length = text.length() - entity.offset; } TextStyleSpan.TextStyleRun newRun = new TextStyleSpan.TextStyleRun(); newRun.start = entity.offset; newRun.end = newRun.start + entity.length; TLRPC.MessageEntity urlEntity = null; if (entity instanceof TLRPC.TL_messageEntityStrike) { newRun.flags = TextStyleSpan.FLAG_STYLE_STRIKE; } else if (entity instanceof TLRPC.TL_messageEntityUnderline) { newRun.flags = TextStyleSpan.FLAG_STYLE_UNDERLINE; } else if (entity instanceof TLRPC.TL_messageEntityBlockquote) { newRun.flags = TextStyleSpan.FLAG_STYLE_QUOTE; } else if (entity instanceof TLRPC.TL_messageEntityBold) { newRun.flags = TextStyleSpan.FLAG_STYLE_BOLD; } else if (entity instanceof TLRPC.TL_messageEntityItalic) { newRun.flags = TextStyleSpan.FLAG_STYLE_ITALIC; } else if (entity instanceof TLRPC.TL_messageEntityCode || entity instanceof TLRPC.TL_messageEntityPre) { newRun.flags = TextStyleSpan.FLAG_STYLE_MONO; } else if (entity instanceof TLRPC.TL_messageEntityMentionName) { newRun.flags = TextStyleSpan.FLAG_STYLE_MENTION; newRun.urlEntity = entity; } else if (entity instanceof TLRPC.TL_inputMessageEntityMentionName) { newRun.flags = TextStyleSpan.FLAG_STYLE_MENTION; newRun.urlEntity = entity; } else { newRun.flags = TextStyleSpan.FLAG_STYLE_URL; newRun.urlEntity = entity; } for (int b = 0, N2 = runs.size(); b < N2; b++) { TextStyleSpan.TextStyleRun run = runs.get(b); if (newRun.start > run.start) { if (newRun.start >= run.end) { continue; } if (newRun.end < run.end) { TextStyleSpan.TextStyleRun r = new TextStyleSpan.TextStyleRun(newRun); r.merge(run); b++; N2++; runs.add(b, r); r = new TextStyleSpan.TextStyleRun(run); r.start = newRun.end; b++; N2++; runs.add(b, r); } else if (newRun.end >= run.end) { TextStyleSpan.TextStyleRun r = new TextStyleSpan.TextStyleRun(newRun); r.merge(run); r.end = run.end; b++; N2++; runs.add(b, r); } int temp = newRun.start; newRun.start = run.end; run.end = temp; } else { if (run.start >= newRun.end) { continue; } int temp = run.start; if (newRun.end == run.end) { run.merge(newRun); } else if (newRun.end < run.end) { TextStyleSpan.TextStyleRun r = new TextStyleSpan.TextStyleRun(run); r.merge(newRun); r.end = newRun.end; b++; N2++; runs.add(b, r); run.start = newRun.end; } else { TextStyleSpan.TextStyleRun r = new TextStyleSpan.TextStyleRun(newRun); r.start = run.end; b++; N2++; runs.add(b, r); run.merge(newRun); } newRun.end = temp; } } if (newRun.start < newRun.end) { runs.add(newRun); } } return runs; } public ArrayList getEntities(CharSequence[] message, boolean allowStrike) { if (message == null || message[0] == null) { return null; } ArrayList entities = null; int index; int start = -1; int lastIndex = 0; boolean isPre = false; final String mono = "`"; final String pre = "```"; final String bold = "**"; final String italic = "__"; final String strike = "~~"; while ((index = TextUtils.indexOf(message[0], !isPre ? mono : pre, lastIndex)) != -1) { if (start == -1) { isPre = message[0].length() - index > 2 && message[0].charAt(index + 1) == '`' && message[0].charAt(index + 2) == '`'; start = index; lastIndex = index + (isPre ? 3 : 1); } else { if (entities == null) { entities = new ArrayList<>(); } for (int a = index + (isPre ? 3 : 1); a < message[0].length(); a++) { if (message[0].charAt(a) == '`') { index++; } else { break; } } lastIndex = index + (isPre ? 3 : 1); if (isPre) { int firstChar = start > 0 ? message[0].charAt(start - 1) : 0; boolean replacedFirst = firstChar == ' ' || firstChar == '\n'; CharSequence startMessage = substring(message[0], 0, start - (replacedFirst ? 1 : 0)); CharSequence content = substring(message[0], start + 3, index); firstChar = index + 3 < message[0].length() ? message[0].charAt(index + 3) : 0; CharSequence endMessage = substring(message[0], index + 3 + (firstChar == ' ' || firstChar == '\n' ? 1 : 0), message[0].length()); if (startMessage.length() != 0) { startMessage = AndroidUtilities.concat(startMessage, "\n"); } else { replacedFirst = true; } if (endMessage.length() != 0) { endMessage = AndroidUtilities.concat("\n", endMessage); } if (!TextUtils.isEmpty(content)) { message[0] = AndroidUtilities.concat(startMessage, content, endMessage); TLRPC.TL_messageEntityPre entity = new TLRPC.TL_messageEntityPre(); entity.offset = start + (replacedFirst ? 0 : 1); entity.length = index - start - 3 + (replacedFirst ? 0 : 1); entity.language = ""; entities.add(entity); lastIndex -= 6; } } else { if (start + 1 != index) { message[0] = AndroidUtilities.concat(substring(message[0], 0, start), substring(message[0], start + 1, index), substring(message[0], index + 1, message[0].length())); TLRPC.TL_messageEntityCode entity = new TLRPC.TL_messageEntityCode(); entity.offset = start; entity.length = index - start - 1; entities.add(entity); lastIndex -= 2; } } start = -1; isPre = false; } } if (start != -1 && isPre) { message[0] = AndroidUtilities.concat(substring(message[0], 0, start), substring(message[0], start + 2, message[0].length())); if (entities == null) { entities = new ArrayList<>(); } TLRPC.TL_messageEntityCode entity = new TLRPC.TL_messageEntityCode(); entity.offset = start; entity.length = 1; entities.add(entity); } if (message[0] instanceof Spanned) { Spanned spannable = (Spanned) message[0]; TextStyleSpan[] spans = spannable.getSpans(0, message[0].length(), TextStyleSpan.class); if (spans != null && spans.length > 0) { for (int a = 0; a < spans.length; a++) { TextStyleSpan span = spans[a]; int spanStart = spannable.getSpanStart(span); int spanEnd = spannable.getSpanEnd(span); if (checkInclusion(spanStart, entities, false) || checkInclusion(spanEnd, entities, true) || checkIntersection(spanStart, spanEnd, entities)) { continue; } if (entities == null) { entities = new ArrayList<>(); } int flags = span.getStyleFlags(); if ((flags & TextStyleSpan.FLAG_STYLE_BOLD) != 0) { TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityBold(); entity.offset = spanStart; entity.length = spanEnd - spanStart; entities.add(entity); } if ((flags & TextStyleSpan.FLAG_STYLE_ITALIC) != 0) { TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityItalic(); entity.offset = spanStart; entity.length = spanEnd - spanStart; entities.add(entity); } if ((flags & TextStyleSpan.FLAG_STYLE_MONO) != 0) { TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityCode(); entity.offset = spanStart; entity.length = spanEnd - spanStart; entities.add(entity); } if ((flags & TextStyleSpan.FLAG_STYLE_STRIKE) != 0) { TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityStrike(); entity.offset = spanStart; entity.length = spanEnd - spanStart; entities.add(entity); } if ((flags & TextStyleSpan.FLAG_STYLE_UNDERLINE) != 0) { TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityUnderline(); entity.offset = spanStart; entity.length = spanEnd - spanStart; entities.add(entity); } if ((flags & TextStyleSpan.FLAG_STYLE_QUOTE) != 0) { TLRPC.MessageEntity entity = new TLRPC.TL_messageEntityBlockquote(); entity.offset = spanStart; entity.length = spanEnd - spanStart; entities.add(entity); } } } URLSpanUserMention[] spansMentions = spannable.getSpans(0, message[0].length(), URLSpanUserMention.class); if (spansMentions != null && spansMentions.length > 0) { if (entities == null) { entities = new ArrayList<>(); } for (int b = 0; b < spansMentions.length; b++) { TLRPC.TL_inputMessageEntityMentionName entity = new TLRPC.TL_inputMessageEntityMentionName(); entity.user_id = getMessagesController().getInputUser(Utilities.parseInt(spansMentions[b].getURL())); if (entity.user_id != null) { entity.offset = spannable.getSpanStart(spansMentions[b]); entity.length = Math.min(spannable.getSpanEnd(spansMentions[b]), message[0].length()) - entity.offset; if (message[0].charAt(entity.offset + entity.length - 1) == ' ') { entity.length--; } entities.add(entity); } } } URLSpanReplacement[] spansUrlReplacement = spannable.getSpans(0, message[0].length(), URLSpanReplacement.class); if (spansUrlReplacement != null && spansUrlReplacement.length > 0) { if (entities == null) { entities = new ArrayList<>(); } for (int b = 0; b < spansUrlReplacement.length; b++) { TLRPC.TL_messageEntityTextUrl entity = new TLRPC.TL_messageEntityTextUrl(); entity.offset = spannable.getSpanStart(spansUrlReplacement[b]); entity.length = Math.min(spannable.getSpanEnd(spansUrlReplacement[b]), message[0].length()) - entity.offset; entity.url = spansUrlReplacement[b].getURL(); entities.add(entity); } } } int count = allowStrike ? 3 : 2; for (int c = 0; c < count; c++) { lastIndex = 0; start = -1; String checkString; char checkChar; switch (c) { case 0: checkString = bold; checkChar = '*'; break; case 1: checkString = italic; checkChar = '_'; break; case 2: default: checkString = strike; checkChar = '~'; break; } while ((index = TextUtils.indexOf(message[0], checkString, lastIndex)) != -1) { if (start == -1) { char prevChar = index == 0 ? ' ' : message[0].charAt(index - 1); if (!checkInclusion(index, entities, false) && (prevChar == ' ' || prevChar == '\n')) { start = index; } lastIndex = index + 2; } else { for (int a = index + 2; a < message[0].length(); a++) { if (message[0].charAt(a) == checkChar) { index++; } else { break; } } lastIndex = index + 2; if (checkInclusion(index, entities, false) || checkIntersection(start, index, entities)) { start = -1; continue; } if (start + 2 != index) { if (entities == null) { entities = new ArrayList<>(); } try { message[0] = AndroidUtilities.concat(substring(message[0], 0, start), substring(message[0], start + 2, index), substring(message[0], index + 2, message[0].length())); } catch (Exception e) { message[0] = substring(message[0], 0, start).toString() + substring(message[0], start + 2, index).toString() + substring(message[0], index + 2, message[0].length()).toString(); } TLRPC.MessageEntity entity; if (c == 0) { entity = new TLRPC.TL_messageEntityBold(); } else if (c == 1) { entity = new TLRPC.TL_messageEntityItalic(); } else { entity = new TLRPC.TL_messageEntityStrike(); } entity.offset = start; entity.length = index - start - 2; removeOffsetAfter(entity.offset + entity.length, 4, entities); entities.add(entity); lastIndex -= 4; } start = -1; } } } return entities; } //---------------- MESSAGES END ---------------- private LongSparseArray draftsFolderIds = new LongSparseArray<>(); private LongSparseArray> drafts = new LongSparseArray<>(); private LongSparseArray> draftMessages = new LongSparseArray<>(); private boolean inTransaction; private SharedPreferences draftPreferences; private boolean loadingDrafts; public void loadDraftsIfNeed() { if (getUserConfig().draftsLoaded || loadingDrafts) { return; } loadingDrafts = true; getConnectionsManager().sendRequest(new TLRPC.TL_messages_getAllDrafts(), (response, error) -> { if (error != null) { AndroidUtilities.runOnUIThread(() -> loadingDrafts = false); } else { getMessagesController().processUpdates((TLRPC.Updates) response, false); AndroidUtilities.runOnUIThread(() -> { loadingDrafts = false; final UserConfig userConfig = getUserConfig(); userConfig.draftsLoaded = true; userConfig.saveConfig(false); }); } }); } public int getDraftFolderId(long did) { return draftsFolderIds.get(did, 0); } public void setDraftFolderId(long did, int folderId) { draftsFolderIds.put(did, folderId); } public void clearDraftsFolderIds() { draftsFolderIds.clear(); } public LongSparseArray> getDrafts() { return drafts; } public TLRPC.DraftMessage getDraft(long did, int threadId) { SparseArray threads = drafts.get(did); if (threads == null) { return null; } return threads.get(threadId); } public TLRPC.Message getDraftMessage(long did, int threadId) { SparseArray threads = draftMessages.get(did); if (threads == null) { return null; } return threads.get(threadId); } public void saveDraft(long did, int threadId, CharSequence message, ArrayList entities, TLRPC.Message replyToMessage, boolean noWebpage) { saveDraft(did, threadId, message, entities, replyToMessage, noWebpage, false); } public void saveDraft(long did, int threadId, CharSequence message, ArrayList entities, TLRPC.Message replyToMessage, boolean noWebpage, boolean clean) { TLRPC.DraftMessage draftMessage; if (!TextUtils.isEmpty(message) || replyToMessage != null) { draftMessage = new TLRPC.TL_draftMessage(); } else { draftMessage = new TLRPC.TL_draftMessageEmpty(); } draftMessage.date = (int) (System.currentTimeMillis() / 1000); draftMessage.message = message == null ? "" : message.toString(); draftMessage.no_webpage = noWebpage; if (replyToMessage != null) { draftMessage.reply_to_msg_id = replyToMessage.id; draftMessage.flags |= 1; } if (entities != null && !entities.isEmpty()) { draftMessage.entities = entities; draftMessage.flags |= 8; } SparseArray threads = drafts.get(did); TLRPC.DraftMessage currentDraft = threads == null ? null : threads.get(threadId); if (!clean) { if (currentDraft != null && currentDraft.message.equals(draftMessage.message) && currentDraft.reply_to_msg_id == draftMessage.reply_to_msg_id && currentDraft.no_webpage == draftMessage.no_webpage || currentDraft == null && TextUtils.isEmpty(draftMessage.message) && draftMessage.reply_to_msg_id == 0) { return; } } saveDraft(did, threadId, draftMessage, replyToMessage, false); if (threadId == 0) { int lower_id = (int) did; if (lower_id != 0) { TLRPC.TL_messages_saveDraft req = new TLRPC.TL_messages_saveDraft(); req.peer = getMessagesController().getInputPeer(lower_id); if (req.peer == null) { return; } req.message = draftMessage.message; req.no_webpage = draftMessage.no_webpage; req.reply_to_msg_id = draftMessage.reply_to_msg_id; req.entities = draftMessage.entities; req.flags = draftMessage.flags; getConnectionsManager().sendRequest(req, (response, error) -> { }); } getMessagesController().sortDialogs(null); getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } } public void saveDraft(long did, int threadId, TLRPC.DraftMessage draft, TLRPC.Message replyToMessage, boolean fromServer) { SharedPreferences.Editor editor = draftPreferences.edit(); final MessagesController messagesController = getMessagesController(); if (draft == null || draft instanceof TLRPC.TL_draftMessageEmpty) { { SparseArray threads = drafts.get(did); if (threads != null) { threads.remove(threadId); if (threads.size() == 0) { drafts.remove(did); } } } { SparseArray threads = draftMessages.get(did); if (threads != null) { threads.remove(threadId); if (threads.size() == 0) { draftMessages.remove(did); } } } if (threadId == 0) { draftPreferences.edit().remove("" + did).remove("r_" + did).commit(); } else { draftPreferences.edit().remove("t_" + did + "_" + threadId).remove("rt_" + did + "_" + threadId).commit(); } messagesController.removeDraftDialogIfNeed(did); } else { SparseArray threads = drafts.get(did); if (threads == null) { threads = new SparseArray<>(); drafts.put(did, threads); } threads.put(threadId, draft); if (threadId == 0) { messagesController.putDraftDialogIfNeed(did, draft); } try { SerializedData serializedData = new SerializedData(draft.getObjectSize()); draft.serializeToStream(serializedData); editor.putString(threadId == 0 ? ("" + did) : ("t_" + did + "_" + threadId), Utilities.bytesToHex(serializedData.toByteArray())); serializedData.cleanup(); } catch (Exception e) { FileLog.e(e); } } SparseArray threads = draftMessages.get(did); if (replyToMessage == null) { if (threads != null) { threads.remove(threadId); if (threads.size() == 0) { draftMessages.remove(did); } } if (threadId == 0) { editor.remove("r_" + did); } else { editor.remove("rt_" + did + "_" + threadId); } } else { if (threads == null) { threads = new SparseArray<>(); draftMessages.put(did, threads); } threads.put(threadId, replyToMessage); SerializedData serializedData = new SerializedData(replyToMessage.getObjectSize()); replyToMessage.serializeToStream(serializedData); editor.putString(threadId == 0 ? ("r_" + did) : ("rt_" + did + "_" + threadId), Utilities.bytesToHex(serializedData.toByteArray())); serializedData.cleanup(); } editor.commit(); if (fromServer && threadId == 0) { if (draft.reply_to_msg_id != 0 && replyToMessage == null) { int lower_id = (int) did; TLRPC.User user = null; TLRPC.Chat chat = null; if (lower_id > 0) { user = getMessagesController().getUser(lower_id); } else { chat = getMessagesController().getChat(-lower_id); } if (user != null || chat != null) { long messageId = draft.reply_to_msg_id; final int channelIdFinal; if (ChatObject.isChannel(chat)) { messageId |= ((long) chat.id) << 32; channelIdFinal = chat.id; } else { channelIdFinal = 0; } final long messageIdFinal = messageId; getMessagesStorage().getStorageQueue().postRunnable(() -> { try { TLRPC.Message message = null; SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data FROM messages WHERE mid = %d", messageIdFinal)); if (cursor.next()) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); message.readAttachPath(data, getUserConfig().clientUserId); data.reuse(); } } cursor.dispose(); if (message == null) { if (channelIdFinal != 0) { final TLRPC.TL_channels_getMessages req = new TLRPC.TL_channels_getMessages(); req.channel = getMessagesController().getInputChannel(channelIdFinal); req.id.add((int) messageIdFinal); getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; if (!messagesRes.messages.isEmpty()) { saveDraftReplyMessage(did, threadId, messagesRes.messages.get(0)); } } }); } else { TLRPC.TL_messages_getMessages req = new TLRPC.TL_messages_getMessages(); req.id.add((int) messageIdFinal); getConnectionsManager().sendRequest(req, (response, error) -> { if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; if (!messagesRes.messages.isEmpty()) { saveDraftReplyMessage(did, threadId, messagesRes.messages.get(0)); } } }); } } else { saveDraftReplyMessage(did, threadId, message); } } catch (Exception e) { FileLog.e(e); } }); } } getNotificationCenter().postNotificationName(NotificationCenter.newDraftReceived, did); } } private void saveDraftReplyMessage(final long did, int threadId, final TLRPC.Message message) { if (message == null) { return; } AndroidUtilities.runOnUIThread(() -> { SparseArray threads = drafts.get(did); TLRPC.DraftMessage draftMessage = threads != null ? threads.get(threadId) : null; if (draftMessage != null && draftMessage.reply_to_msg_id == message.id) { SparseArray threads2 = draftMessages.get(did); if (threads2 == null) { threads2 = new SparseArray<>(); draftMessages.put(did, threads2); } threads2.put(threadId, message); SerializedData serializedData = new SerializedData(message.getObjectSize()); message.serializeToStream(serializedData); draftPreferences.edit().putString(threadId == 0 ? ("r_" + did) : ("rt_" + did + "_" + threadId), Utilities.bytesToHex(serializedData.toByteArray())).commit(); getNotificationCenter().postNotificationName(NotificationCenter.newDraftReceived, did); serializedData.cleanup(); } }); } public void clearAllDrafts(boolean notify) { drafts.clear(); draftMessages.clear(); draftsFolderIds.clear(); draftPreferences.edit().clear().commit(); if (notify) { getMessagesController().sortDialogs(null); getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } } public void cleanDraft(long did, int threadId, boolean replyOnly) { SparseArray threads2 = drafts.get(did); TLRPC.DraftMessage draftMessage = threads2 != null ? threads2.get(threadId) : null; if (draftMessage == null) { return; } if (!replyOnly) { { SparseArray threads = drafts.get(did); if (threads != null) { threads.remove(threadId); if (threads.size() == 0) { drafts.remove(did); } } } { SparseArray threads = draftMessages.get(did); if (threads != null) { threads.remove(threadId); if (threads.size() == 0) { draftMessages.remove(did); } } } if (threadId == 0) { draftPreferences.edit().remove("" + did).remove("r_" + did).commit(); getMessagesController().sortDialogs(null); getNotificationCenter().postNotificationName(NotificationCenter.dialogsNeedReload); } else { draftPreferences.edit().remove("t_" + did + "_" + threadId).remove("rt_" + did + "_" + threadId).commit(); } } else if (draftMessage.reply_to_msg_id != 0) { draftMessage.reply_to_msg_id = 0; draftMessage.flags &= ~1; saveDraft(did, threadId, draftMessage.message, draftMessage.entities, null, draftMessage.no_webpage, true); } } public void beginTransaction() { inTransaction = true; } public void endTransaction() { inTransaction = false; } //---------------- DRAFT END ---------------- private SparseArray botInfos = new SparseArray<>(); private LongSparseArray botKeyboards = new LongSparseArray<>(); private SparseLongArray botKeyboardsByMids = new SparseLongArray(); public void clearBotKeyboard(final long did, final ArrayList messages) { AndroidUtilities.runOnUIThread(() -> { if (messages != null) { for (int a = 0; a < messages.size(); a++) { long did1 = botKeyboardsByMids.get(messages.get(a)); if (did1 != 0) { botKeyboards.remove(did1); botKeyboardsByMids.delete(messages.get(a)); getNotificationCenter().postNotificationName(NotificationCenter.botKeyboardDidLoad, null, did1); } } } else { botKeyboards.remove(did); getNotificationCenter().postNotificationName(NotificationCenter.botKeyboardDidLoad, null, did); } }); } public void loadBotKeyboard(final long did) { TLRPC.Message keyboard = botKeyboards.get(did); if (keyboard != null) { getNotificationCenter().postNotificationName(NotificationCenter.botKeyboardDidLoad, keyboard, did); return; } getMessagesStorage().getStorageQueue().postRunnable(() -> { try { TLRPC.Message botKeyboard = null; SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT info FROM bot_keyboard WHERE uid = %d", did)); if (cursor.next()) { NativeByteBuffer data; if (!cursor.isNull(0)) { data = cursor.byteBufferValue(0); if (data != null) { botKeyboard = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } } } cursor.dispose(); if (botKeyboard != null) { final TLRPC.Message botKeyboardFinal = botKeyboard; AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.botKeyboardDidLoad, botKeyboardFinal, did)); } } catch (Exception e) { FileLog.e(e); } }); } public void loadBotInfo(final int uid, boolean cache, final int classGuid) { if (cache) { TLRPC.BotInfo botInfo = botInfos.get(uid); if (botInfo != null) { getNotificationCenter().postNotificationName(NotificationCenter.botInfoDidLoad, botInfo, classGuid); return; } } getMessagesStorage().getStorageQueue().postRunnable(() -> { try { TLRPC.BotInfo botInfo = null; SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT info FROM bot_info WHERE uid = %d", uid)); if (cursor.next()) { NativeByteBuffer data; if (!cursor.isNull(0)) { data = cursor.byteBufferValue(0); if (data != null) { botInfo = TLRPC.BotInfo.TLdeserialize(data, data.readInt32(false), false); data.reuse(); } } } cursor.dispose(); if (botInfo != null) { final TLRPC.BotInfo botInfoFinal = botInfo; AndroidUtilities.runOnUIThread(() -> getNotificationCenter().postNotificationName(NotificationCenter.botInfoDidLoad, botInfoFinal, classGuid)); } } catch (Exception e) { FileLog.e(e); } }); } public void putBotKeyboard(final long did, final TLRPC.Message message) { if (message == null) { return; } try { int mid = 0; SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized(String.format(Locale.US, "SELECT mid FROM bot_keyboard WHERE uid = %d", did)); if (cursor.next()) { mid = cursor.intValue(0); } cursor.dispose(); if (mid >= message.id) { return; } SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO bot_keyboard VALUES(?, ?, ?)"); state.requery(); NativeByteBuffer data = new NativeByteBuffer(message.getObjectSize()); message.serializeToStream(data); state.bindLong(1, did); state.bindInteger(2, message.id); state.bindByteBuffer(3, data); state.step(); data.reuse(); state.dispose(); AndroidUtilities.runOnUIThread(() -> { TLRPC.Message old = botKeyboards.get(did); botKeyboards.put(did, message); if (old != null) { botKeyboardsByMids.delete(old.id); } botKeyboardsByMids.put(message.id, did); getNotificationCenter().postNotificationName(NotificationCenter.botKeyboardDidLoad, message, did); }); } catch (Exception e) { FileLog.e(e); } } public void putBotInfo(final TLRPC.BotInfo botInfo) { if (botInfo == null) { return; } botInfos.put(botInfo.user_id, botInfo); getMessagesStorage().getStorageQueue().postRunnable(() -> { try { SQLitePreparedStatement state = getMessagesStorage().getDatabase().executeFast("REPLACE INTO bot_info(uid, info) VALUES(?, ?)"); state.requery(); NativeByteBuffer data = new NativeByteBuffer(botInfo.getObjectSize()); botInfo.serializeToStream(data); state.bindInteger(1, botInfo.user_id); state.bindByteBuffer(2, data); state.step(); data.reuse(); state.dispose(); } catch (Exception e) { FileLog.e(e); } }); } //---------------- BOT END ---------------- //---------------- EMOJI START ---------------- public static class KeywordResult { public String emoji; public String keyword; } public interface KeywordResultCallback { void run(ArrayList param, String alias); } private HashMap currentFetchingEmoji = new HashMap<>(); public void fetchNewEmojiKeywords(String[] langCodes) { if (langCodes == null) { return; } for (int a = 0; a < langCodes.length; a++) { String langCode = langCodes[a]; if (TextUtils.isEmpty(langCode)) { return; } if (currentFetchingEmoji.get(langCode) != null) { return; } currentFetchingEmoji.put(langCode, true); getMessagesStorage().getStorageQueue().postRunnable(() -> { int version = -1; String alias = null; long date = 0; try { SQLiteCursor cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT alias, version, date FROM emoji_keywords_info_v2 WHERE lang = ?", langCode); if (cursor.next()) { alias = cursor.stringValue(0); version = cursor.intValue(1); date = cursor.longValue(2); } cursor.dispose(); } catch (Exception e) { FileLog.e(e); } if (!BuildVars.DEBUG_VERSION && Math.abs(System.currentTimeMillis() - date) < 60 * 60 * 1000) { AndroidUtilities.runOnUIThread(() -> currentFetchingEmoji.remove(langCode)); return; } TLObject request; if (version == -1) { TLRPC.TL_messages_getEmojiKeywords req = new TLRPC.TL_messages_getEmojiKeywords(); req.lang_code = langCode; request = req; } else { TLRPC.TL_messages_getEmojiKeywordsDifference req = new TLRPC.TL_messages_getEmojiKeywordsDifference(); req.lang_code = langCode; req.from_version = version; request = req; } String aliasFinal = alias; int versionFinal = version; getConnectionsManager().sendRequest(request, (response, error) -> { if (response != null) { TLRPC.TL_emojiKeywordsDifference res = (TLRPC.TL_emojiKeywordsDifference) response; if (versionFinal != -1 && !res.lang_code.equals(aliasFinal)) { getMessagesStorage().getStorageQueue().postRunnable(() -> { try { SQLitePreparedStatement deleteState = getMessagesStorage().getDatabase().executeFast("DELETE FROM emoji_keywords_info_v2 WHERE lang = ?"); deleteState.bindString(1, langCode); deleteState.step(); deleteState.dispose(); AndroidUtilities.runOnUIThread(() -> { currentFetchingEmoji.remove(langCode); fetchNewEmojiKeywords(new String[]{langCode}); }); } catch (Exception e) { FileLog.e(e); } }); } else { putEmojiKeywords(langCode, res); } } else { AndroidUtilities.runOnUIThread(() -> currentFetchingEmoji.remove(langCode)); } }); }); } } private void putEmojiKeywords(String lang, TLRPC.TL_emojiKeywordsDifference res) { if (res == null) { return; } getMessagesStorage().getStorageQueue().postRunnable(() -> { try { if (!res.keywords.isEmpty()) { SQLitePreparedStatement insertState = getMessagesStorage().getDatabase().executeFast("REPLACE INTO emoji_keywords_v2 VALUES(?, ?, ?)"); SQLitePreparedStatement deleteState = getMessagesStorage().getDatabase().executeFast("DELETE FROM emoji_keywords_v2 WHERE lang = ? AND keyword = ? AND emoji = ?"); getMessagesStorage().getDatabase().beginTransaction(); for (int a = 0, N = res.keywords.size(); a < N; a++) { TLRPC.EmojiKeyword keyword = res.keywords.get(a); if (keyword instanceof TLRPC.TL_emojiKeyword) { TLRPC.TL_emojiKeyword emojiKeyword = (TLRPC.TL_emojiKeyword) keyword; String key = emojiKeyword.keyword.toLowerCase(); for (int b = 0, N2 = emojiKeyword.emoticons.size(); b < N2; b++) { insertState.requery(); insertState.bindString(1, res.lang_code); insertState.bindString(2, key); insertState.bindString(3, emojiKeyword.emoticons.get(b)); insertState.step(); } } else if (keyword instanceof TLRPC.TL_emojiKeywordDeleted) { TLRPC.TL_emojiKeywordDeleted keywordDeleted = (TLRPC.TL_emojiKeywordDeleted) keyword; String key = keywordDeleted.keyword.toLowerCase(); for (int b = 0, N2 = keywordDeleted.emoticons.size(); b < N2; b++) { deleteState.requery(); deleteState.bindString(1, res.lang_code); deleteState.bindString(2, key); deleteState.bindString(3, keywordDeleted.emoticons.get(b)); deleteState.step(); } } } getMessagesStorage().getDatabase().commitTransaction(); insertState.dispose(); deleteState.dispose(); } SQLitePreparedStatement infoState = getMessagesStorage().getDatabase().executeFast("REPLACE INTO emoji_keywords_info_v2 VALUES(?, ?, ?, ?)"); infoState.bindString(1, lang); infoState.bindString(2, res.lang_code); infoState.bindInteger(3, res.version); infoState.bindLong(4, System.currentTimeMillis()); infoState.step(); infoState.dispose(); AndroidUtilities.runOnUIThread(() -> { currentFetchingEmoji.remove(lang); getNotificationCenter().postNotificationName(NotificationCenter.newEmojiSuggestionsAvailable, lang); }); } catch (Exception e) { FileLog.e(e); } }); } public void getEmojiSuggestions(String[] langCodes, String keyword, boolean fullMatch, KeywordResultCallback callback) { getEmojiSuggestions(langCodes, keyword, fullMatch, callback, null); } public void getEmojiSuggestions(String[] langCodes, String keyword, boolean fullMatch, KeywordResultCallback callback, CountDownLatch sync) { if (callback == null) { return; } if (TextUtils.isEmpty(keyword) || langCodes == null) { callback.run(new ArrayList<>(), null); return; } ArrayList recentEmoji = new ArrayList<>(Emoji.recentEmoji); getMessagesStorage().getStorageQueue().postRunnable(() -> { ArrayList result = new ArrayList<>(); HashMap resultMap = new HashMap<>(); String alias = null; try { SQLiteCursor cursor; boolean hasAny = false; for (int a = 0; a < langCodes.length; a++) { cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT alias FROM emoji_keywords_info_v2 WHERE lang = ?", langCodes[a]); if (cursor.next()) { alias = cursor.stringValue(0); } cursor.dispose(); if (alias != null) { hasAny = true; } } if (!hasAny) { AndroidUtilities.runOnUIThread(() -> { for (int a = 0; a < langCodes.length; a++) { if (currentFetchingEmoji.get(langCodes[a]) != null) { return; } } callback.run(result, null); }); return; } String key = keyword.toLowerCase(); for (int a = 0; a < 2; a++) { if (a == 1) { String translitKey = LocaleController.getInstance().getTranslitString(key, false, false); if (translitKey.equals(key)) { continue; } key = translitKey; } String key2 = null; StringBuilder nextKey = new StringBuilder(key); int pos = nextKey.length(); while (pos > 0) { pos--; char value = nextKey.charAt(pos); value++; nextKey.setCharAt(pos, value); if (value != 0) { key2 = nextKey.toString(); break; } } if (fullMatch) { cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT emoji, keyword FROM emoji_keywords_v2 WHERE keyword = ?", key); } else if (key2 != null) { cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT emoji, keyword FROM emoji_keywords_v2 WHERE keyword >= ? AND keyword < ?", key, key2); } else { key += "%"; cursor = getMessagesStorage().getDatabase().queryFinalized("SELECT emoji, keyword FROM emoji_keywords_v2 WHERE keyword LIKE ?", key); } while (cursor.next()) { String value = cursor.stringValue(0).replace("\ufe0f", ""); if (resultMap.get(value) != null) { continue; } resultMap.put(value, true); KeywordResult keywordResult = new KeywordResult(); keywordResult.emoji = value; keywordResult.keyword = cursor.stringValue(1); result.add(keywordResult); } cursor.dispose(); } } catch (Exception e) { FileLog.e(e); } Collections.sort(result, (o1, o2) -> { int idx1 = recentEmoji.indexOf(o1.emoji); if (idx1 < 0) { idx1 = Integer.MAX_VALUE; } int idx2 = recentEmoji.indexOf(o2.emoji); if (idx2 < 0) { idx2 = Integer.MAX_VALUE; } if (idx1 < idx2) { return -1; } else if (idx1 > idx2) { return 1; } else { int len1 = o1.keyword.length(); int len2 = o2.keyword.length(); if (len1 < len2) { return -1; } else if (len1 > len2) { return 1; } return 0; } }); String aliasFinal = alias; if (sync != null) { callback.run(result, aliasFinal); sync.countDown(); } else { AndroidUtilities.runOnUIThread(() -> callback.run(result, aliasFinal)); } }); if (sync != null) { try { sync.await(); } catch (Throwable ignore) { } } } //---------------- EMOJI END ---------------- }