From c9037885c77a56c1bbf628d60e0644fcce14049d Mon Sep 17 00:00:00 2001 From: Matvei Plokhov Date: Tue, 14 Jul 2026 13:41:02 +0200 Subject: [PATCH 1/2] Direct secondary users to Owner user for MMS --- .../BaseConversationMessageRenderingTest.kt | 2 + ...tionMessageUiModelMapperMmsDownloadTest.kt | 1 + ...veConversationMessageSimDisplayNameTest.kt | 1 + res/values/strings.xml | 6 ++ .../SubscriptionSettingsUiStateMapper.kt | 2 + .../subscription/model/SubscriptionUiState.kt | 1 + .../ui/SubscriptionSettingsScreen.kt | 79 +++++++++++++------ .../ConversationMessageUiModelMapper.kt | 2 + .../model/message/MmsDownloadUiModel.kt | 1 + .../ui/message/ConversationMmsDownloadBody.kt | 71 +++++++++++++---- .../preview/ConversationPreviewData.kt | 2 + 11 files changed, 132 insertions(+), 36 deletions(-) diff --git a/app/src/sharedTest/kotlin/com/android/messaging/ui/conversation/messages/ui/message/rendering/BaseConversationMessageRenderingTest.kt b/app/src/sharedTest/kotlin/com/android/messaging/ui/conversation/messages/ui/message/rendering/BaseConversationMessageRenderingTest.kt index dcc46cc37..d238dc2cf 100644 --- a/app/src/sharedTest/kotlin/com/android/messaging/ui/conversation/messages/ui/message/rendering/BaseConversationMessageRenderingTest.kt +++ b/app/src/sharedTest/kotlin/com/android/messaging/ui/conversation/messages/ui/message/rendering/BaseConversationMessageRenderingTest.kt @@ -162,11 +162,13 @@ internal abstract class BaseConversationMessageRenderingTest { state: MmsDownloadUiModel.State = MmsDownloadUiModel.State.AwaitingManualDownload, sizeBytes: Long = MMS_SIZE_BYTES, expiryTimestamp: Long = MMS_EXPIRY_TIMESTAMP, + isSecondaryUser: Boolean = false, ): MmsDownloadUiModel { return MmsDownloadUiModel( state = state, sizeBytes = sizeBytes, expiryTimestamp = expiryTimestamp, + isSecondaryUser = isSecondaryUser, ) } diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperMmsDownloadTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperMmsDownloadTest.kt index 04ce8ad3f..a2b651711 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperMmsDownloadTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/mapper/conversationmessage/ConversationMessageUiModelMapperMmsDownloadTest.kt @@ -27,6 +27,7 @@ internal class ConversationMessageUiModelMapperMmsDownloadTest : state = MmsDownloadUiModel.State.AwaitingManualDownload, sizeBytes = 2_048L, expiryTimestamp = 1_700_000_000_000L, + isSecondaryUser = false, ), uiModel.mmsDownload, ) diff --git a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ResolveConversationMessageSimDisplayNameTest.kt b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ResolveConversationMessageSimDisplayNameTest.kt index 6904f3df2..eb4d0355e 100644 --- a/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ResolveConversationMessageSimDisplayNameTest.kt +++ b/app/src/test/kotlin/com/android/messaging/ui/conversation/messages/ui/message/ResolveConversationMessageSimDisplayNameTest.kt @@ -152,6 +152,7 @@ class ResolveConversationMessageSimDisplayNameTest { state = MmsDownloadUiModel.State.AwaitingManualDownload, sizeBytes = 0L, expiryTimestamp = 0L, + isSecondaryUser = false, ) } diff --git a/res/values/strings.xml b/res/values/strings.xml index c9c5c2445..db68736a6 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -197,6 +197,10 @@ Downloading… Message expired or not available + + MMS not available for this user + + Download it from the Owner user size: %1$s, expiration: %2$s @@ -494,6 +498,8 @@ Roaming auto-retrieve Automatically retrieve MMS when roaming + + Only the Owner user can download MMS Group messaging Use MMS to send a single message when there are multiple recipients diff --git a/src/com/android/messaging/ui/appsettings/subscription/mapper/SubscriptionSettingsUiStateMapper.kt b/src/com/android/messaging/ui/appsettings/subscription/mapper/SubscriptionSettingsUiStateMapper.kt index e29dce5d3..64695fdbc 100644 --- a/src/com/android/messaging/ui/appsettings/subscription/mapper/SubscriptionSettingsUiStateMapper.kt +++ b/src/com/android/messaging/ui/appsettings/subscription/mapper/SubscriptionSettingsUiStateMapper.kt @@ -6,6 +6,7 @@ import com.android.messaging.data.subscriptionsettings.model.PerSubscriptionData import com.android.messaging.data.subscriptionsettings.model.SubscriptionSettingsData import com.android.messaging.ui.appsettings.subscription.model.SubscriptionSettingsUiState import com.android.messaging.ui.appsettings.subscription.model.SubscriptionUiState +import com.android.messaging.util.OsUtil import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import kotlinx.collections.immutable.ImmutableList @@ -121,6 +122,7 @@ internal class SubscriptionSettingsUiStateMapperImpl @Inject constructor( deliveryReportsEnabled = perSub.deliveryReportsEnabled, isWirelessAlertsSupported = perSub.showCellBroadcast && isCellBroadcastAppEnabled, isDefaultSmsApp = isDefaultSmsApp, + isSecondaryUser = OsUtil.isSecondaryUser(), ) } } diff --git a/src/com/android/messaging/ui/appsettings/subscription/model/SubscriptionUiState.kt b/src/com/android/messaging/ui/appsettings/subscription/model/SubscriptionUiState.kt index 1d5732dfe..7bea7bf00 100644 --- a/src/com/android/messaging/ui/appsettings/subscription/model/SubscriptionUiState.kt +++ b/src/com/android/messaging/ui/appsettings/subscription/model/SubscriptionUiState.kt @@ -17,4 +17,5 @@ internal data class SubscriptionUiState( val deliveryReportsEnabled: Boolean = false, val isWirelessAlertsSupported: Boolean = false, val isDefaultSmsApp: Boolean = false, + val isSecondaryUser: Boolean = false, ) diff --git a/src/com/android/messaging/ui/appsettings/subscription/ui/SubscriptionSettingsScreen.kt b/src/com/android/messaging/ui/appsettings/subscription/ui/SubscriptionSettingsScreen.kt index cbc78e885..220462bf9 100644 --- a/src/com/android/messaging/ui/appsettings/subscription/ui/SubscriptionSettingsScreen.kt +++ b/src/com/android/messaging/ui/appsettings/subscription/ui/SubscriptionSettingsScreen.kt @@ -1,5 +1,6 @@ package com.android.messaging.ui.appsettings.subscription.ui +import androidx.annotation.StringRes import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -169,31 +170,65 @@ private fun LazyListScope.mmsSettingsItems( ) } - item(key = "auto_retrieve_mms") { - SettingsSwitchItem( - title = stringResource(R.string.auto_retrieve_mms_pref_title), - summary = stringResource(R.string.auto_retrieve_mms_pref_summary), - checked = subscriptionSettings.autoRetrieveMms, - enabled = subscriptionSettings.isDefaultSmsApp, - onCheckedChange = { enabled -> - onAction( - Action.AutoRetrieveMmsChanged(subscriptionSettings.subId, enabled), - ) - }, - ) - } + autoRetrieveMmsItems( + subscriptionSettings = subscriptionSettings, + onAction = onAction, + ) +} - item(key = "auto_retrieve_mms_roaming") { +private fun LazyListScope.autoRetrieveMmsItems( + subscriptionSettings: SubscriptionUiState, + onAction: (Action) -> Unit, +) { + autoRetrieveSwitchItem( + key = "auto_retrieve_mms", + subscriptionSettings = subscriptionSettings, + titleResId = R.string.auto_retrieve_mms_pref_title, + summaryResId = R.string.auto_retrieve_mms_pref_summary, + checked = subscriptionSettings.autoRetrieveMms, + dependencyEnabled = true, + onCheckedChange = { enabled -> + onAction(Action.AutoRetrieveMmsChanged(subscriptionSettings.subId, enabled)) + }, + ) + + autoRetrieveSwitchItem( + key = "auto_retrieve_mms_roaming", + subscriptionSettings = subscriptionSettings, + titleResId = R.string.auto_retrieve_mms_when_roaming_pref_title, + summaryResId = R.string.auto_retrieve_mms_when_roaming_pref_summary, + checked = subscriptionSettings.autoRetrieveMmsWhenRoaming, + dependencyEnabled = subscriptionSettings.autoRetrieveMms, + onCheckedChange = { enabled -> + onAction(Action.AutoRetrieveMmsWhenRoamingChanged(subscriptionSettings.subId, enabled)) + }, + ) +} + +private fun LazyListScope.autoRetrieveSwitchItem( + key: String, + subscriptionSettings: SubscriptionUiState, + @StringRes titleResId: Int, + @StringRes summaryResId: Int, + checked: Boolean, + dependencyEnabled: Boolean, + onCheckedChange: (Boolean) -> Unit, +) { + item(key = key) { SettingsSwitchItem( - title = stringResource(R.string.auto_retrieve_mms_when_roaming_pref_title), - summary = stringResource(R.string.auto_retrieve_mms_when_roaming_pref_summary), - checked = subscriptionSettings.autoRetrieveMmsWhenRoaming, - enabled = subscriptionSettings.isDefaultSmsApp && subscriptionSettings.autoRetrieveMms, - onCheckedChange = { enabled -> - onAction( - Action.AutoRetrieveMmsWhenRoamingChanged(subscriptionSettings.subId, enabled), - ) + title = stringResource(titleResId), + summary = when { + subscriptionSettings.isSecondaryUser -> { + stringResource(R.string.auto_retrieve_mms_secondary_user_summary) + } + + else -> stringResource(summaryResId) }, + checked = checked, + enabled = subscriptionSettings.isDefaultSmsApp && + dependencyEnabled && + !subscriptionSettings.isSecondaryUser, + onCheckedChange = onCheckedChange, ) } } diff --git a/src/com/android/messaging/ui/conversation/messages/mapper/ConversationMessageUiModelMapper.kt b/src/com/android/messaging/ui/conversation/messages/mapper/ConversationMessageUiModelMapper.kt index a9098cfed..d89c574bd 100644 --- a/src/com/android/messaging/ui/conversation/messages/mapper/ConversationMessageUiModelMapper.kt +++ b/src/com/android/messaging/ui/conversation/messages/mapper/ConversationMessageUiModelMapper.kt @@ -10,6 +10,7 @@ import com.android.messaging.ui.conversation.messages.model.message.Conversation import com.android.messaging.ui.conversation.messages.model.message.MmsDownloadUiModel import com.android.messaging.util.ContentType import com.android.messaging.util.LogUtil +import com.android.messaging.util.OsUtil import javax.inject.Inject import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -93,6 +94,7 @@ internal class ConversationMessageUiModelMapperImpl @Inject constructor( state = state, sizeBytes = data.smsMessageSize.toLong(), expiryTimestamp = data.mmsExpiry, + isSecondaryUser = OsUtil.isSecondaryUser(), ) } } diff --git a/src/com/android/messaging/ui/conversation/messages/model/message/MmsDownloadUiModel.kt b/src/com/android/messaging/ui/conversation/messages/model/message/MmsDownloadUiModel.kt index 0f7289efa..7bcb13144 100644 --- a/src/com/android/messaging/ui/conversation/messages/model/message/MmsDownloadUiModel.kt +++ b/src/com/android/messaging/ui/conversation/messages/model/message/MmsDownloadUiModel.kt @@ -7,6 +7,7 @@ internal data class MmsDownloadUiModel( val state: State, val sizeBytes: Long, val expiryTimestamp: Long, + val isSecondaryUser: Boolean, ) { enum class State { AwaitingManualDownload, diff --git a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMmsDownloadBody.kt b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMmsDownloadBody.kt index d3257c25f..f074b0eb3 100644 --- a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMmsDownloadBody.kt +++ b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMmsDownloadBody.kt @@ -37,12 +37,26 @@ internal fun ConversationMmsDownloadBody( isSelected = isSelected, contentColor = contentColor, ) + val isSecondaryUserReferral = isSecondaryUserDownloadReferral( + state = download.state, + isSecondaryUser = download.isSecondaryUser, + ) ConversationMmsDownloadBodyContent( - titleText = stringResource(id = mmsDownloadTitleResId(state = download.state)), + titleText = stringResource( + id = mmsDownloadTitleResId( + state = download.state, + isSecondaryUserReferral = isSecondaryUserReferral, + ), + ), infoText = rememberMmsDownloadInfoText(download = download), statusLineText = rememberMmsDownloadStatusLineText( - statusText = stringResource(id = mmsDownloadStatusResId(state = download.state)), + statusText = stringResource( + id = mmsDownloadStatusResId( + state = download.state, + isSecondaryUserReferral = isSecondaryUserReferral, + ), + ), simDisplayName = simDisplayName, ), contentColor = contentColor, @@ -50,6 +64,7 @@ internal fun ConversationMmsDownloadBody( statusColor = mmsDownloadStatusColor( state = download.state, canDownloadMessage = canDownloadMessage, + isSecondaryUserReferral = isSecondaryUserReferral, isSelected = isSelected, contentColor = contentColor, supportingColor = supportingColor, @@ -144,28 +159,37 @@ private fun rememberMmsDownloadStatusLineText( private fun mmsDownloadStatusColor( state: MmsDownloadUiModel.State, canDownloadMessage: Boolean, + isSecondaryUserReferral: Boolean, isSelected: Boolean, contentColor: Color, supportingColor: Color, ): Color { + val canRetryDownload = canDownloadMessage && + ( + state == MmsDownloadUiModel.State.AwaitingManualDownload || + state == MmsDownloadUiModel.State.DownloadFailed + ) + val isDownloadError = state == MmsDownloadUiModel.State.DownloadFailed || + state == MmsDownloadUiModel.State.ExpiredOrUnavailable + return when { isSelected -> contentColor - state == MmsDownloadUiModel.State.AwaitingManualDownload && canDownloadMessage -> { - MaterialTheme.colorScheme.primary - } - state == MmsDownloadUiModel.State.DownloadFailed && canDownloadMessage -> { - MaterialTheme.colorScheme.primary - } - state == MmsDownloadUiModel.State.DownloadFailed -> MaterialTheme.colorScheme.error - state == MmsDownloadUiModel.State.ExpiredOrUnavailable -> { - MaterialTheme.colorScheme.error - } + isSecondaryUserReferral -> supportingColor + canRetryDownload -> MaterialTheme.colorScheme.primary + isDownloadError -> MaterialTheme.colorScheme.error else -> supportingColor } } @StringRes -private fun mmsDownloadTitleResId(state: MmsDownloadUiModel.State): Int { +private fun mmsDownloadTitleResId( + state: MmsDownloadUiModel.State, + isSecondaryUserReferral: Boolean, +): Int { + if (isSecondaryUserReferral) { + return R.string.message_title_download_secondary_user + } + return when (state) { MmsDownloadUiModel.State.AwaitingManualDownload -> { R.string.message_title_manual_download @@ -179,7 +203,14 @@ private fun mmsDownloadTitleResId(state: MmsDownloadUiModel.State): Int { } @StringRes -private fun mmsDownloadStatusResId(state: MmsDownloadUiModel.State): Int { +private fun mmsDownloadStatusResId( + state: MmsDownloadUiModel.State, + isSecondaryUserReferral: Boolean, +): Int { + if (isSecondaryUserReferral) { + return R.string.message_status_download_secondary_user + } + return when (state) { MmsDownloadUiModel.State.AwaitingManualDownload -> { R.string.message_status_download @@ -192,6 +223,18 @@ private fun mmsDownloadStatusResId(state: MmsDownloadUiModel.State): Int { } } +private fun isSecondaryUserDownloadReferral( + state: MmsDownloadUiModel.State, + isSecondaryUser: Boolean, +): Boolean { + return isSecondaryUser && when (state) { + MmsDownloadUiModel.State.AwaitingManualDownload -> true + MmsDownloadUiModel.State.DownloadFailed -> true + MmsDownloadUiModel.State.Downloading -> false + MmsDownloadUiModel.State.ExpiredOrUnavailable -> false + } +} + private fun buildMmsDownloadInfoText( context: Context, download: MmsDownloadUiModel, diff --git a/src/com/android/messaging/ui/conversation/preview/ConversationPreviewData.kt b/src/com/android/messaging/ui/conversation/preview/ConversationPreviewData.kt index 9ae96f745..2d2eb92e7 100644 --- a/src/com/android/messaging/ui/conversation/preview/ConversationPreviewData.kt +++ b/src/com/android/messaging/ui/conversation/preview/ConversationPreviewData.kt @@ -491,11 +491,13 @@ internal fun previewInlineVCardAttachment( internal fun previewMmsDownloadUiModel( state: MmsDownloadUiModel.State = MmsDownloadUiModel.State.AwaitingManualDownload, + isSecondaryUser: Boolean = false, ): MmsDownloadUiModel { return MmsDownloadUiModel( state = state, sizeBytes = 2_400_000L, expiryTimestamp = PREVIEW_NOW_MILLIS + 86_400_000L, + isSecondaryUser = isSecondaryUser, ) } From 17473f67baa941cb13997cdcb3b9dcbece09cc33 Mon Sep 17 00:00:00 2001 From: Matvei Plokhov Date: Tue, 14 Jul 2026 13:52:55 +0200 Subject: [PATCH 2/2] Fix seeded MMS download messages being deleted by sync --- .../messaging/datamodel/action/DownloadMmsAction.java | 5 +++++ src/com/android/messaging/debug/TestDataSeeder.kt | 1 - src/com/android/messaging/sms/MmsUtils.java | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/com/android/messaging/datamodel/action/DownloadMmsAction.java b/src/com/android/messaging/datamodel/action/DownloadMmsAction.java index 2852a6121..e866a84a5 100644 --- a/src/com/android/messaging/datamodel/action/DownloadMmsAction.java +++ b/src/com/android/messaging/datamodel/action/DownloadMmsAction.java @@ -118,6 +118,11 @@ protected boolean queueAction(final String messageId, final Action processingAct final MessageData message = BugleDatabaseOperations.readMessage(db, messageId); if (message != null && message.canDownloadMessage()) { final Uri notificationUri = message.getSmsMessageUri(); + if (notificationUri == null) { + LogUtil.w(TAG, "DownloadMmsAction: message " + messageId + + " has no notification uri; skipping download"); + return false; + } final String conversationId = message.getConversationId(); final int status = message.getStatus(); diff --git a/src/com/android/messaging/debug/TestDataSeeder.kt b/src/com/android/messaging/debug/TestDataSeeder.kt index 6a0efc779..a6c9e02a4 100644 --- a/src/com/android/messaging/debug/TestDataSeeder.kt +++ b/src/com/android/messaging/debug/TestDataSeeder.kt @@ -957,7 +957,6 @@ private fun insertMmsDownloadMessage( put(MessageColumns.RECEIVED_TIMESTAMP, timestamp) put(MessageColumns.SEEN, 1) put(MessageColumns.READ, 1) - put(MessageColumns.SMS_MESSAGE_URI, "content://mms/${900_000 + seedIndex}") put(MessageColumns.SMS_PRIORITY, 0) put(MessageColumns.SMS_MESSAGE_SIZE, messageSizeBytes) put(MessageColumns.MMS_TRANSACTION_ID, "seeded-transaction-$seedIndex") diff --git a/src/com/android/messaging/sms/MmsUtils.java b/src/com/android/messaging/sms/MmsUtils.java index 5eebec830..71986cd9f 100644 --- a/src/com/android/messaging/sms/MmsUtils.java +++ b/src/com/android/messaging/sms/MmsUtils.java @@ -1951,6 +1951,9 @@ public static StatusPlusUri updateSentMmsMessageStatus(final Context context, } public static void clearMmsStatus(final Context context, final Uri uri) { + if (uri == null) { + return; + } // Messaging application can leave invalid values in STATUS field of M-Notification.ind // messages. Take this opportunity to clear it. // Downloading status just kept in local db and not reflected into telephony.