diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationIntents.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationIntents.java index 2aa13bb130..47cf65a631 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationIntents.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationIntents.java @@ -159,7 +159,6 @@ public class ConversationIntents { } public @Nullable ChatWallpaper getWallpaper() { - // TODO [greyson][wallpaper] Is it worth it to do this beforehand? return Recipient.resolved(recipientId).getWallpaper(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 62a758fdb3..0c8e6d75ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -163,6 +163,7 @@ import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask; import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; import org.thoughtcrime.securesms.util.views.Stub; +import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState; import java.lang.ref.WeakReference; @@ -1034,7 +1035,15 @@ public class ConversationListFragment extends MainFragment implements ActionMode } private void handleCreateConversation(long threadId, Recipient recipient, int distributionType) { - getNavigator().goToConversation(recipient.getId(), threadId, distributionType, -1); + SimpleTask.run(getLifecycle(), () -> { + ChatWallpaper wallpaper = recipient.resolve().getWallpaper(); + if (wallpaper != null && !wallpaper.prefetch(requireContext(), 250)) { + Log.w(TAG, "Failed to prefetch wallpaper."); + } + return null; + }, (nothing) -> { + getNavigator().goToConversation(recipient.getId(), threadId, distributionType, -1); + }); } private void startActionMode() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaper.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaper.java index 53ef7adcc0..9d5336b921 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaper.java @@ -1,9 +1,11 @@ package org.thoughtcrime.securesms.wallpaper; +import android.content.Context; import android.os.Parcelable; import android.widget.ImageView; import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; import org.thoughtcrime.securesms.conversation.colors.ChatColors; import org.thoughtcrime.securesms.conversation.colors.ChatColorsMapper; @@ -26,6 +28,11 @@ public interface ChatWallpaper extends Parcelable { void loadInto(@NonNull ImageView imageView); + @WorkerThread + default boolean prefetch(@NonNull Context context, long maxWaitTime) { + return true; + } + default boolean isPhoto() { return false; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/UriChatWallpaper.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/UriChatWallpaper.java index 041df2e31f..8790323521 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/UriChatWallpaper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/UriChatWallpaper.java @@ -1,9 +1,12 @@ package org.thoughtcrime.securesms.wallpaper; +import android.content.Context; +import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import android.util.LruCache; import android.widget.ImageView; import androidx.annotation.NonNull; @@ -18,11 +21,26 @@ import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; import org.thoughtcrime.securesms.mms.GlideApp; +import org.thoughtcrime.securesms.util.ByteUnit; +import org.thoughtcrime.securesms.util.LRUCache; +import java.util.HashMap; import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; final class UriChatWallpaper implements ChatWallpaper, Parcelable { + private static final LruCache CACHE = new LruCache((int) Runtime.getRuntime().maxMemory() / 8) { + @Override + protected int sizeOf(Uri key, Bitmap value) { + return value.getByteCount(); + } + }; + private static final String TAG = Log.tag(UriChatWallpaper.class); private final Uri uri; @@ -45,22 +63,58 @@ final class UriChatWallpaper implements ChatWallpaper, Parcelable { @Override public void loadInto(@NonNull ImageView imageView) { - GlideApp.with(imageView) - .load(new DecryptableStreamUriLoader.DecryptableUri(uri)) - .addListener(new RequestListener() { - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - Log.w(TAG, "Failed to load wallpaper " + uri); - return false; - } + Bitmap cached = CACHE.get(uri); + if (cached != null) { + Log.d(TAG, "Using cached value."); + imageView.setImageBitmap(CACHE.get(uri)); + } else { + Log.d(TAG, "Not in cache. Fetching using Glide."); + GlideApp.with(imageView) + .load(new DecryptableStreamUriLoader.DecryptableUri(uri)) + .addListener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + Log.w(TAG, "Failed to load wallpaper " + uri); + return false; + } - @Override - public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { - Log.i(TAG, "Loaded wallpaper " + uri); - return false; - } - }) - .into(imageView); + @Override + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + Log.i(TAG, "Loaded wallpaper " + uri); + return false; + } + }) + .into(imageView); + } + } + + @Override + public boolean prefetch(@NonNull Context context, long maxWaitTime) { + Bitmap cached = CACHE.get(uri); + if (cached != null) { + Log.d(TAG, "Already cached, skipping prefetch."); + return true; + } + + long startTime = System.currentTimeMillis(); + try { + Bitmap bitmap = GlideApp.with(context) + .asBitmap() + .load(new DecryptableStreamUriLoader.DecryptableUri(uri)) + .submit() + .get(maxWaitTime, TimeUnit.MILLISECONDS); + + CACHE.put(uri, bitmap); + Log.d(TAG, "Prefetched wallpaper in " + (System.currentTimeMillis() - startTime) + " ms."); + + return true; + } catch (ExecutionException | InterruptedException e) { + Log.w(TAG, "Failed to prefetch wallpaper.", e); + } catch (TimeoutException e) { + Log.w(TAG, "Timed out waiting for prefetch."); + } + + return false; } @Override