Fix wallpaper ANR regression while maintaining correct incoming message bubble colors.

This commit is contained in:
jeffrey-signal
2026-04-13 07:56:00 -04:00
committed by Cody Henthorne
parent 01705459cf
commit 629b96dd20
11 changed files with 65 additions and 36 deletions

View File

@@ -13,12 +13,9 @@ import org.signal.core.models.UriSerializer
import org.signal.core.models.media.Media
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.conversation.ConversationIntents.ConversationScreenType
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.mms.SlideFactory
import org.thoughtcrime.securesms.recipients.Recipient.Companion.resolved
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stickers.StickerLocator
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
@Serializable
@Parcelize
@@ -38,19 +35,12 @@ data class ConversationArgs(
val giftBadge: Badge?,
val shareDataTimestamp: Long,
val conversationScreenType: ConversationScreenType,
val isIncognito: Boolean = false
val isIncognito: Boolean = false,
val hasWallpaper: Boolean = false
) : Parcelable {
@IgnoredOnParcel
val draftMediaType: SlideFactory.MediaType? = SlideFactory.MediaType.from(draftContentType)
@IgnoredOnParcel
val wallpaper: ChatWallpaper?
get() = resolved(recipientId).wallpaper
@IgnoredOnParcel
val chatColors: ChatColors
get() = resolved(recipientId).chatColors
fun canInitializeFromDatabase(): Boolean {
return draftText == null && (draftMedia == null || ConversationIntents.isBubbleIntentUri(draftMedia) || ConversationIntents.isNotificationIntentUri(draftMedia)) && draftMediaType == null
}

View File

@@ -10,11 +10,11 @@ import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.badges.models.Badge;
import org.signal.core.models.media.Media;
import org.thoughtcrime.securesms.MainActivity;
import org.thoughtcrime.securesms.badges.models.Badge;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadTable;
import org.signal.core.models.media.Media;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.stickers.StickerLocator;
@@ -48,6 +48,7 @@ public class ConversationIntents {
private static final String EXTRA_SHARE_DATA_TIMESTAMP = "share_data_timestamp";
private static final String EXTRA_CONVERSATION_TYPE = "conversation_type";
private static final String EXTRA_INCOGNITO = "incognito";
private static final String EXTRA_HAS_WALLPAPER = "has_wallpaper";
private static final String INTENT_DATA = "intent_data";
private static final String INTENT_TYPE = "intent_type";
@@ -75,12 +76,15 @@ public class ConversationIntents {
}
}
public static @NonNull Builder createPopUpBuilder(@NonNull Context context, @NonNull RecipientId recipientId, long threadId) {
return new Builder(context, ConversationPopupActivity.class, recipientId, threadId, ConversationScreenType.POPUP);
public static @NonNull Builder createPopUpBuilder(@NonNull Context context, @NonNull RecipientId recipientId, long threadId, boolean hasWallpaper) {
return new Builder(context, ConversationPopupActivity.class, recipientId, threadId, ConversationScreenType.POPUP)
.withHasWallpaper(hasWallpaper);
}
public static @NonNull Intent createBubbleIntent(@NonNull Context context, @NonNull RecipientId recipientId, long threadId) {
return new Builder(context, BubbleConversationActivity.class, recipientId, threadId, ConversationScreenType.BUBBLE).build();
public static @NonNull Intent createBubbleIntent(@NonNull Context context, @NonNull RecipientId recipientId, long threadId, boolean hasWallpaper) {
return new Builder(context, BubbleConversationActivity.class, recipientId, threadId, ConversationScreenType.BUBBLE)
.withHasWallpaper(hasWallpaper)
.build();
}
/**
@@ -156,7 +160,9 @@ public class ConversationIntents {
null,
-1L,
ConversationScreenType.BUBBLE,
false);
false,
Boolean.parseBoolean(intentDataUri.getQueryParameter(EXTRA_HAS_WALLPAPER))
);
}
return new ConversationArgs(RecipientId.from(Objects.requireNonNull(arguments.getString(EXTRA_RECIPIENT))),
@@ -174,7 +180,8 @@ public class ConversationIntents {
arguments.getParcelable(EXTRA_GIFT_BADGE),
arguments.getLong(EXTRA_SHARE_DATA_TIMESTAMP, -1L),
ConversationScreenType.from(arguments.getInt(EXTRA_CONVERSATION_TYPE, 0)),
arguments.getBoolean(EXTRA_INCOGNITO, false));
arguments.getBoolean(EXTRA_INCOGNITO, false),
arguments.getBoolean(EXTRA_HAS_WALLPAPER, false));
}
public final static class Builder {
@@ -197,6 +204,7 @@ public class ConversationIntents {
private Badge giftBadge;
private long shareDataTimestamp = -1L;
private boolean incognito;
private boolean hasWallpaper;
private int flags;
private Builder(@NonNull Context context,
@@ -226,6 +234,7 @@ public class ConversationIntents {
giftBadge = args.getGiftBadge();
shareDataTimestamp = args.getShareDataTimestamp();
incognito = args.isIncognito();
hasWallpaper = args.getHasWallpaper();
return this;
}
@@ -295,6 +304,11 @@ public class ConversationIntents {
return this;
}
public @NonNull Builder withHasWallpaper(boolean hasWallpaper) {
this.hasWallpaper = hasWallpaper;
return this;
}
public @NonNull Builder withFlags(int flags) {
this.flags = flags;
return this;
@@ -317,7 +331,8 @@ public class ConversationIntents {
giftBadge,
shareDataTimestamp,
conversationScreenType,
incognito
incognito,
hasWallpaper
);
}
@@ -337,6 +352,7 @@ public class ConversationIntents {
intent.setData(new Uri.Builder().authority(BUBBLE_AUTHORITY)
.appendQueryParameter(EXTRA_RECIPIENT, recipientId.serialize())
.appendQueryParameter(EXTRA_THREAD_ID, String.valueOf(threadId))
.appendQueryParameter(EXTRA_HAS_WALLPAPER, String.valueOf(hasWallpaper))
.build());
return intent;
@@ -353,6 +369,7 @@ public class ConversationIntents {
intent.putExtra(EXTRA_SHARE_DATA_TIMESTAMP, shareDataTimestamp);
intent.putExtra(EXTRA_CONVERSATION_TYPE, conversationScreenType.code);
intent.putExtra(EXTRA_INCOGNITO, incognito);
intent.putExtra(EXTRA_HAS_WALLPAPER, hasWallpaper);
if (draftText != null) {
intent.putExtra(EXTRA_TEXT, draftText);

View File

@@ -480,8 +480,7 @@ class ConversationFragment :
repository = ConversationRepository(localContext = requireContext(), isInBubble = args.conversationScreenType == ConversationScreenType.BUBBLE),
recipientRepository = conversationRecipientRepository,
messageRequestRepository = messageRequestRepository,
scheduledMessagesRepository = ScheduledMessagesRepository(),
initialChatColors = args.chatColors
scheduledMessagesRepository = ScheduledMessagesRepository()
)
}
@@ -687,8 +686,6 @@ class ConversationFragment :
incognito = args.isIncognito
)
conversationToolbarOnScrollHelper.attach(binding.conversationItemRecycler)
presentWallpaper(args.wallpaper)
presentChatColors(args.chatColors)
presentConversationTitle(viewModel.recipientSnapshot)
presentGroupConversationSubtitle(createGroupSubtitleString(viewModel.titleViewParticipantsSnapshot))
presentActionBarMenu()
@@ -1683,6 +1680,11 @@ class ConversationFragment :
presentChatColors(recipient.chatColors)
invalidateOptionsMenu()
updateMessageRequestAcceptedState(!viewModel.hasMessageRequestState)
recyclerViewColorizer.setChatColors(recipient.chatColors)
if (adapter.onHasWallpaperChanged(hasWallpaper = recipient.wallpaper != null)) {
conversationItemDecorations.hasWallpaper = recipient.wallpaper != null
}
}
@MainThread
@@ -2149,7 +2151,7 @@ class ConversationFragment :
lifecycleOwner = viewLifecycleOwner,
requestManager = Glide.with(this),
clickListener = ConversationItemClickListener(),
hasWallpaper = args.wallpaper != null,
hasWallpaper = args.hasWallpaper,
colorizer = colorizer,
startExpirationTimeout = viewModel::startExpirationTimeout,
chatColorsDataProvider = viewModel::chatColorsSnapshot,
@@ -2166,7 +2168,7 @@ class ConversationFragment :
adapter.setPagingController(viewModel.pagingController)
recyclerViewColorizer = RecyclerViewColorizer(binding.conversationItemRecycler)
recyclerViewColorizer.setChatColors(args.chatColors)
viewModel.recipientSnapshot?.chatColors?.let { recyclerViewColorizer.setChatColors(it) }
binding.conversationItemRecycler.adapter = ConcatAdapter(typingIndicatorAdapter, adapter)
multiselectItemDecoration = MultiselectItemDecoration(
@@ -2203,7 +2205,7 @@ class ConversationFragment :
threadHeaderMarginDecoration.toolbarMargin = statusBarInset + resources.getDimensionPixelSize(R.dimen.signal_m3_toolbar_height) + 16.dp
binding.conversationItemRecycler.addItemDecoration(threadHeaderMarginDecoration)
conversationItemDecorations = ConversationItemDecorations(hasWallpaper = args.wallpaper != null)
conversationItemDecorations = ConversationItemDecorations(hasWallpaper = args.hasWallpaper)
binding.conversationItemRecycler.addItemDecoration(conversationItemDecorations, 0)
}

View File

@@ -59,7 +59,6 @@ import org.thoughtcrime.securesms.banner.banners.UnauthorizedBanner
import org.thoughtcrime.securesms.contactshare.Contact
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.ScheduledMessagesRepository
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.conversation.plaintext.PlaintextExportRepository
import org.thoughtcrime.securesms.conversation.v2.data.ConversationElementKey
@@ -112,7 +111,6 @@ import kotlin.time.Duration
class ConversationViewModel(
val threadId: Long,
requestedStartingPosition: Int,
initialChatColors: ChatColors,
private val repository: ConversationRepository,
recipientRepository: ConversationRecipientRepository,
messageRequestRepository: MessageRequestRepository,
@@ -160,7 +158,7 @@ class ConversationViewModel(
.observeOn(AndroidSchedulers.mainThread())
private val chatBounds: BehaviorSubject<Rect> = BehaviorSubject.create()
private val chatColors: RxStore<ChatColorsDrawable.ChatColorsData> = RxStore(ChatColorsDrawable.ChatColorsData(initialChatColors, null))
private val chatColors: RxStore<ChatColorsDrawable.ChatColorsData> = RxStore(ChatColorsDrawable.ChatColorsData(null, null))
val chatColorsSnapshot: ChatColorsDrawable.ChatColorsData get() = chatColors.state
@Volatile

View File

@@ -252,7 +252,7 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
hasProcessedSupportedPayload = true
}
if (hasProcessedSupportedPayload) {
if (hasProcessedSupportedPayload && V2Payload.WALLPAPER !in payload) {
return
}

View File

@@ -253,13 +253,14 @@ class MainNavigationViewModel(
}
private fun goToConversation(args: ConversationArgs) = viewModelScope.launch {
withContext(Dispatchers.IO) {
val updatedArgs = withContext(Dispatchers.IO) {
val wallpaper = Recipient.resolved(args.recipientId).wallpaper
if (wallpaper?.prefetch(AppDependencies.application, 250) == false) {
Log.w(TAG, "goToConversation: Failed to prefetch wallpaper.")
}
args.copy(hasWallpaper = wallpaper != null)
}
internalDetailLocation.emit(MainNavigationDetailLocation.Chats.Conversation(args))
internalDetailLocation.emit(MainNavigationDetailLocation.Chats.Conversation(updatedArgs))
}
fun goToCameraFirstStoryCapture() {

View File

@@ -6,6 +6,7 @@ import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
@@ -28,6 +29,8 @@ public interface MessageNotifier {
void cancelDelayedNotifications();
void updateNotification(@NonNull Context context);
void updateNotification(@NonNull Context context, @NonNull ConversationId conversationId);
@WorkerThread
void forceBubbleNotification(@NonNull Context context, @NonNull ConversationId conversationId);
void addStickyThread(@NonNull ConversationId conversationId, long earliestTimestamp);
void removeStickyThread(@NonNull ConversationId conversationId);

View File

@@ -8,6 +8,7 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import android.service.notification.StatusBarNotification
import androidx.annotation.WorkerThread
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.content.ContextCompat
import me.leolin.shortcutbadger.ShortcutBadger
@@ -114,10 +115,12 @@ class DefaultMessageNotifier(context: Application) : MessageNotifier {
executor.cancel()
}
@WorkerThread
override fun updateNotification(context: Context) {
updateNotification(context, null, BubbleState.HIDDEN)
}
@WorkerThread
override fun updateNotification(context: Context, conversationId: ConversationId) {
if (System.currentTimeMillis() - lastDesktopActivityTimestamp < DESKTOP_ACTIVITY_PERIOD) {
Log.i(TAG, "Scheduling delayed notification...")
@@ -127,10 +130,12 @@ class DefaultMessageNotifier(context: Application) : MessageNotifier {
}
}
@WorkerThread
override fun forceBubbleNotification(context: Context, conversationId: ConversationId) {
updateNotification(context, conversationId, BubbleState.SHOWN)
}
@WorkerThread
private fun updateNotification(
context: Context,
conversationId: ConversationId?,

View File

@@ -11,6 +11,7 @@ import android.text.TextUtils
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.annotation.WorkerThread
import androidx.core.app.NotificationCompat
import androidx.core.app.RemoteInput
import androidx.core.content.LocusIdCompat
@@ -83,6 +84,8 @@ sealed class NotificationBuilder(protected val context: Context) {
protected abstract fun addMarkAsReadActionActual(state: NotificationState)
protected abstract fun addMessagesActual(conversation: NotificationConversation, includeShortcut: Boolean)
protected abstract fun addMessagesActual(state: NotificationState)
@WorkerThread
protected abstract fun setBubbleMetadataActual(conversation: NotificationConversation, bubbleState: BubbleUtil.BubbleState)
protected abstract fun setLights(@ColorInt color: Int, onTime: Int, offTime: Int)
@@ -154,6 +157,7 @@ sealed class NotificationBuilder(protected val context: Context) {
addMessagesActual(state)
}
@WorkerThread
fun setBubbleMetadata(conversation: NotificationConversation, bubbleState: BubbleUtil.BubbleState) {
if (privacy.isDisplayContact && isNotLocked) {
setBubbleMetadataActual(conversation, bubbleState)
@@ -360,15 +364,18 @@ sealed class NotificationBuilder(protected val context: Context) {
}
}
@WorkerThread
override fun setBubbleMetadataActual(conversation: NotificationConversation, bubbleState: BubbleUtil.BubbleState) {
if (Build.VERSION.SDK_INT < ConversationUtil.CONVERSATION_SUPPORT_VERSION) {
return
}
val wallpaper = conversation.recipient.wallpaper
wallpaper?.prefetch(context, 250)
val intent: PendingIntent? = NotificationPendingIntentHelper.getActivity(
context,
0,
ConversationIntents.createBubbleIntent(context, conversation.recipient.id, conversation.thread.threadId),
ConversationIntents.createBubbleIntent(context, conversation.recipient.id, conversation.thread.threadId, wallpaper != null),
mutable()
)

View File

@@ -176,7 +176,7 @@ data class NotificationConversation(
}
fun getQuickReplyIntent(context: Context): PendingIntent? {
val intent: Intent = ConversationIntents.createPopUpBuilder(context, recipient.id, mostRecentNotification.thread.threadId)
val intent: Intent = ConversationIntents.createPopUpBuilder(context, recipient.id, mostRecentNotification.thread.threadId, recipient.wallpaper != null)
.build()
.makeUniqueToPreventMerging()

View File

@@ -11,6 +11,7 @@ import android.media.RingtoneManager
import android.net.Uri
import android.os.Build
import android.os.TransactionTooLargeException
import androidx.annotation.WorkerThread
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
@@ -50,6 +51,7 @@ object NotificationFactory {
private val STILL_DECRYPTING_INDIVIDUAL_THROTTLE: Duration = 5.seconds
private val GROUP_THROTTLE: Duration = 20.seconds
@WorkerThread
fun notify(
context: Context,
state: NotificationState,
@@ -97,6 +99,7 @@ object NotificationFactory {
}
}
@WorkerThread
private fun notify19(
context: Context,
state: NotificationState,
@@ -144,6 +147,7 @@ object NotificationFactory {
return threadsThatNewlyAlerted
}
@WorkerThread
@TargetApi(24)
private fun notify24(
context: Context,
@@ -213,6 +217,7 @@ object NotificationFactory {
return ((conversation.hasNewNotifications() && canAlertBasedOnTime) || alertOverride) && !conversation.mostRecentNotification.authorRecipient.isSelf
}
@WorkerThread
private fun notifyForConversation(
context: Context,
conversation: NotificationConversation,
@@ -446,6 +451,7 @@ object NotificationFactory {
NotificationManagerCompat.from(context).safelyNotify(recipient, NotificationIds.getNotificationIdForMessageDeliveryFailed(thread), builder.build())
}
@WorkerThread
@JvmStatic
fun notifyToBubbleConversation(context: Context, recipient: Recipient, threadId: Long) {
val builder: NotificationBuilder = NotificationBuilder.create(context)