From fcf713e583e49a0dd7af0f4999d2ac6b0f1bc4a7 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 5 Jun 2026 18:54:03 +0200 Subject: [PATCH] Remove built-in YouTube link previews --- .../android/messaging/debug/TestDataSeeder.kt | 3 +- .../ConversationMessageAttachment.kt | 7 -- .../ConversationAttachmentActionDispatcher.kt | 6 -- .../ConversationAttachmentSectionsBuilder.kt | 9 +- .../ConversationVisualAttachments.kt | 16 --- .../ui/message/ConversationMessage.kt | 8 -- .../ui/message/ConversationMessageBubble.kt | 9 -- .../ConversationMessageContentBuilder.kt | 69 +------------ .../ui/message/ConversationMessageRows.kt | 10 -- .../android/messaging/util/YouTubeUtil.java | 98 ------------------- .../messaging/util/YouTubeUtilTest.java | 62 ------------ 11 files changed, 6 insertions(+), 291 deletions(-) delete mode 100644 src/com/android/messaging/util/YouTubeUtil.java delete mode 100644 tests/src/com/android/messaging/util/YouTubeUtilTest.java diff --git a/src/com/android/messaging/debug/TestDataSeeder.kt b/src/com/android/messaging/debug/TestDataSeeder.kt index 3dc7b2a49..b9719d61a 100644 --- a/src/com/android/messaging/debug/TestDataSeeder.kt +++ b/src/com/android/messaging/debug/TestDataSeeder.kt @@ -44,7 +44,6 @@ import kotlinx.coroutines.runBlocking private const val TAG = "TestDataSeeder" private const val TEST_PHONE_PREFIX = "+15550" -private const val TEST_YOUTUBE_VIDEO_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" private const val TEST_LINK_MESSAGE_URL = "https://grapheneos.org" private const val MEDIA_SCRATCH_FILE_EXTENSION_QUERY_PARAMETER = "ext" private const val SEED_IMAGE_1_FILE_ID = "800001" @@ -1358,7 +1357,7 @@ private fun seedScenarioH( ), Msg("text", text = "We all had the same idea haha", senderId = jackId), Msg("image", attachmentUri = img1, senderId = carolId), - Msg("text", text = TEST_YOUTUBE_VIDEO_URL, senderId = carolId), + Msg("text", text = "Found a clip of it online", senderId = carolId), Msg("text", text = "The clip version is even better", senderId = jackId), Msg("video", attachmentUri = videoUri, senderId = carolId), Msg("text", text = "And here's the ambient audio from the room", senderId = jackId), diff --git a/src/com/android/messaging/ui/conversation/messages/model/attachment/ConversationMessageAttachment.kt b/src/com/android/messaging/ui/conversation/messages/model/attachment/ConversationMessageAttachment.kt index 9ff880a35..be5393c26 100644 --- a/src/com/android/messaging/ui/conversation/messages/model/attachment/ConversationMessageAttachment.kt +++ b/src/com/android/messaging/ui/conversation/messages/model/attachment/ConversationMessageAttachment.kt @@ -18,11 +18,4 @@ internal sealed interface ConversationMessageAttachment { override val key: String, val part: ConversationMessagePartUiModel.Attachment, ) : ConversationMessageAttachment - - @Immutable - data class YouTubePreview( - override val key: String, - val sourceUrl: String, - val thumbnailUrl: String, - ) : ConversationMessageAttachment } diff --git a/src/com/android/messaging/ui/conversation/messages/ui/attachment/ConversationAttachmentActionDispatcher.kt b/src/com/android/messaging/ui/conversation/messages/ui/attachment/ConversationAttachmentActionDispatcher.kt index 0f091060f..4e2855403 100644 --- a/src/com/android/messaging/ui/conversation/messages/ui/attachment/ConversationAttachmentActionDispatcher.kt +++ b/src/com/android/messaging/ui/conversation/messages/ui/attachment/ConversationAttachmentActionDispatcher.kt @@ -40,11 +40,5 @@ internal fun ConversationMessageAttachment.toConversationAttachmentOpenActionOrN ) } } - - is ConversationMessageAttachment.YouTubePreview -> { - ConversationAttachmentOpenAction.OpenExternal( - uri = sourceUrl, - ) - } } } diff --git a/src/com/android/messaging/ui/conversation/messages/ui/attachment/ConversationAttachmentSectionsBuilder.kt b/src/com/android/messaging/ui/conversation/messages/ui/attachment/ConversationAttachmentSectionsBuilder.kt index 7df42dc89..51fdf3c2c 100644 --- a/src/com/android/messaging/ui/conversation/messages/ui/attachment/ConversationAttachmentSectionsBuilder.kt +++ b/src/com/android/messaging/ui/conversation/messages/ui/attachment/ConversationAttachmentSectionsBuilder.kt @@ -45,7 +45,6 @@ private fun isGalleryVisualAttachment( attachment.part is ConversationMessagePartUiModel.Attachment.Image } - is ConversationMessageAttachment.YouTubePreview -> true is ConversationMessageAttachment.Unsupported -> false } } @@ -57,9 +56,7 @@ private fun isStandaloneVisualAttachment( is ConversationMessageAttachment.Media -> attachment.part is ConversationMessagePartUiModel.Attachment.Video - is ConversationMessageAttachment.Unsupported, - is ConversationMessageAttachment.YouTubePreview, - -> false + is ConversationMessageAttachment.Unsupported -> false } } @@ -98,8 +95,6 @@ private fun isInlineAttachment( is ConversationMessageAttachment.Media, is ConversationMessageAttachment.Unsupported, -> true - - else -> false } } @@ -122,8 +117,6 @@ private fun toInlineAttachment( openAction = attachment.toConversationAttachmentOpenActionOrNull(), ) } - - is ConversationMessageAttachment.YouTubePreview -> null } } diff --git a/src/com/android/messaging/ui/conversation/messages/ui/attachment/ConversationVisualAttachments.kt b/src/com/android/messaging/ui/conversation/messages/ui/attachment/ConversationVisualAttachments.kt index 88a8f2702..d45ee654c 100644 --- a/src/com/android/messaging/ui/conversation/messages/ui/attachment/ConversationVisualAttachments.kt +++ b/src/com/android/messaging/ui/conversation/messages/ui/attachment/ConversationVisualAttachments.kt @@ -37,7 +37,6 @@ import com.android.messaging.ui.conversation.messages.model.attachment.Conversat import com.android.messaging.ui.conversation.messages.model.message.ConversationMessagePartUiModel import com.android.messaging.ui.conversation.preview.previewMessageAttachments import com.android.messaging.ui.core.MessagingPreviewColumn -import com.android.messaging.util.ContentType import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -278,16 +277,6 @@ private fun ConversationAttachmentThumbnail( ) } - is ConversationMessageAttachment.YouTubePreview -> { - ConversationMediaThumbnail( - modifier = modifier, - contentUri = attachment.thumbnailUrl, - contentType = ContentType.IMAGE_JPEG, - size = thumbnailSize, - contentScale = contentScale, - ) - } - is ConversationMessageAttachment.Unsupported -> { Box( modifier = modifier.background( @@ -356,7 +345,6 @@ private fun ConversationMessageAttachment.requiresPlaybackAffordance(): Boolean is ConversationMessageAttachment.Media -> { part is ConversationMessagePartUiModel.Attachment.Video } - is ConversationMessageAttachment.YouTubePreview -> true is ConversationMessageAttachment.Unsupported -> false } } @@ -369,10 +357,6 @@ private fun resolveAttachmentAspectRatio( resolvePartAspectRatio(part = attachment.part) } - is ConversationMessageAttachment.YouTubePreview -> { - MESSAGE_ATTACHMENT_DEFAULT_VIDEO_ASPECT_RATIO - } - is ConversationMessageAttachment.Unsupported -> { MESSAGE_ATTACHMENT_DEFAULT_IMAGE_ASPECT_RATIO } diff --git a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessage.kt b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessage.kt index caec0cdd1..4cac16f51 100644 --- a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessage.kt +++ b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessage.kt @@ -561,14 +561,6 @@ private fun ConversationMessageAttachmentContentPreview() { ), simDisplayName = "Work", ) - ConversationMessagePreviewItem( - message = previewOutgoingMessage( - messageId = "outgoing-youtube-preview", - text = "Watch this: https://www.youtube.com/watch?v=dQw4w9WgXcQ", - status = Status.Outgoing.Delivered, - ), - simDisplayName = "Personal", - ) } } diff --git a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageBubble.kt b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageBubble.kt index 46f7943b4..bbb1cd2ef 100644 --- a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageBubble.kt +++ b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageBubble.kt @@ -660,15 +660,6 @@ private fun ConversationMessageBubbleAttachmentSurfacePreview() { isSelectionMode = true, simDisplayName = "Work", ) - ConversationMessageBubblePreviewItem( - message = previewOutgoingMessage( - messageId = "bubble-outgoing-youtube", - text = "Watch this: https://www.youtube.com/watch?v=dQw4w9WgXcQ", - status = Status.Outgoing.Delivered, - ), - bubbleLayoutMode = ConversationMessageBubbleLayoutMode.AttachmentsInSurface, - simDisplayName = "Personal", - ) } } diff --git a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilder.kt b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilder.kt index 7aeb16311..9b176f532 100644 --- a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilder.kt +++ b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageContentBuilder.kt @@ -1,23 +1,22 @@ package com.android.messaging.ui.conversation.messages.ui.message import android.net.Uri -import android.util.Patterns -import android.webkit.URLUtil import com.android.messaging.R import com.android.messaging.ui.conversation.messages.model.attachment.ConversationMessageAttachment import com.android.messaging.ui.conversation.messages.model.message.ConversationMessageContent import com.android.messaging.ui.conversation.messages.model.message.ConversationMessagePartUiModel import com.android.messaging.ui.conversation.messages.model.message.ConversationMessageUiModel import com.android.messaging.ui.conversation.messages.ui.attachment.buildConversationAttachmentSections -import com.android.messaging.util.YouTubeUtil -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList internal fun buildConversationMessageContent( message: ConversationMessageUiModel, subjectText: String?, ): ConversationMessageContent { - val attachments = buildConversationMessageAttachments(message = message) + val attachments = message + .parts + .mapIndexedNotNull(::toConversationMessageAttachment) + .toImmutableList() val attachmentSections = buildConversationAttachmentSections( attachments = attachments, vCardSubtitleTextResIdOverride = vCardSubtitleTextResIdOverride(message), @@ -47,31 +46,6 @@ private fun vCardSubtitleTextResIdOverride(message: ConversationMessageUiModel): } } -private fun buildConversationMessageAttachments( - message: ConversationMessageUiModel, -): ImmutableList { - val attachmentItems = message - .parts - .mapIndexedNotNull(::toConversationMessageAttachment) - .toImmutableList() - - val hasImageAttachment = attachmentItems.any { attachment -> - attachment is ConversationMessageAttachment.Media && - attachment.part is ConversationMessagePartUiModel.Attachment.Image - } - - if (hasImageAttachment) { - return attachmentItems - } - - return message.text - ?.let(::findSingleYouTubePreview) - ?.let { youtubePreview -> - (attachmentItems + youtubePreview).toImmutableList() - } - ?: attachmentItems -} - private fun toConversationMessageAttachment( index: Int, part: ConversationMessagePartUiModel, @@ -145,38 +119,3 @@ private fun ConversationMessagePartUiModel.Attachment.isSupportedAttachment(): B is ConversationMessagePartUiModel.Attachment.File -> false } } - -private fun findSingleYouTubePreview( - text: String, -): ConversationMessageAttachment.YouTubePreview? { - return extractConversationWebUrls(text) - .asSequence() - .mapNotNull { sourceUrl -> - val thumbnailUrl = YouTubeUtil - .getYoutubePreviewImageLink(sourceUrl) - ?: return@mapNotNull null - - ConversationMessageAttachment.YouTubePreview( - key = "youtube:$sourceUrl", - sourceUrl = sourceUrl, - thumbnailUrl = thumbnailUrl, - ) - } - .take(2) - .singleOrNull() -} - -private fun extractConversationWebUrls(text: String): Set { - val webUrlMatcher = Patterns.WEB_URL.matcher(text) - val urls = LinkedHashSet() - - while (webUrlMatcher.find()) { - webUrlMatcher - .group() - .takeIf { it.isNotBlank() } - ?.let(URLUtil::guessUrl) - ?.let(urls::add) - } - - return urls -} diff --git a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageRows.kt b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageRows.kt index bbf6b517d..5e80bb6a2 100644 --- a/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageRows.kt +++ b/src/com/android/messaging/ui/conversation/messages/ui/message/ConversationMessageRows.kt @@ -561,16 +561,6 @@ private fun ConversationMessageRowsAttachmentPreview() { simDisplayName = "Work", metadataText = "18:11 \u2022 Failed", ) - - ConversationMessageRowsPreviewItem( - message = previewOutgoingMessage( - messageId = "rows-attachments-youtube-preview", - text = "Reference clip: https://www.youtube.com/watch?v=dQw4w9WgXcQ", - status = Status.Outgoing.Delivered, - ), - simDisplayName = "Personal", - metadataText = "18:12 \u2022 Delivered", - ) } } diff --git a/src/com/android/messaging/util/YouTubeUtil.java b/src/com/android/messaging/util/YouTubeUtil.java deleted file mode 100644 index 203a6669c..000000000 --- a/src/com/android/messaging/util/YouTubeUtil.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.messaging.util; - -import android.net.Uri; -import android.text.TextUtils; - -public class YouTubeUtil { - private static final String YOUTUBE_HOST_1 = "www.youtube.com"; - private static final String YOUTUBE_HOST_2 = "youtube.com"; - private static final String YOUTUBE_HOST_3 = "m.youtube.com"; - private static final String YOUTUBE_HOST_4 = "youtube.googleapis.com"; - private static final String YOUTUBE_HOST_5 = "youtu.be"; - - private static final String YOUTUBE_PATH_1 = "/watch"; - private static final String YOUTUBE_PATH_2 = "/embed/"; - private static final String YOUTUBE_PATH_3 = "/v/"; - private static final String YOUTUBE_PATH_4 = "/apiplayer"; - - public static final String YOUTUBE_STATIC_THUMBNAIL_PREFIX = "https://img.youtube.com/vi/"; - public static final String YOUTUBE_STATIC_THUMBNAIL_END = "/hqdefault.jpg"; - - public static String getYoutubePreviewImageLink(String urlString) { - // Types of youtube urls: - // 1.) http://www.youtube.com/watch?v=VIDEOID - // 2.) http://www.youtube.com/embed/VIDEOID - // 3.) http://www.youtube.com/v/VIDEOID - // 3a.) https://youtube.googleapis.com/v/VIDEOID - // 4.) http://www.youtube.com/apiplayer?video_id=VIDEO_ID - // 5.) http://youtu.be/VIDEOID - if (!urlString.startsWith("http")) { - // Apparently the url is not an RFC 2396 compliant uri without the port - urlString = "http://" + urlString; - } - final Uri uri = Uri.parse(urlString); - final String host = uri.getHost(); - if (YOUTUBE_HOST_1.equalsIgnoreCase(host) - || YOUTUBE_HOST_2.equalsIgnoreCase(host) - || YOUTUBE_HOST_3.equalsIgnoreCase(host) - || YOUTUBE_HOST_4.equalsIgnoreCase(host) - || YOUTUBE_HOST_5.equalsIgnoreCase(host)) { - final String videoId = getYouTubeVideoId(uri); - if (!TextUtils.isEmpty(videoId)) { - return YOUTUBE_STATIC_THUMBNAIL_PREFIX + videoId + YOUTUBE_STATIC_THUMBNAIL_END; - } - return null; - } - return null; - } - - private static String getYouTubeVideoId(Uri uri) { - final String urlPath = uri.getPath(); - - if (TextUtils.isEmpty(urlPath)) { - // There is no path so no need to continue. - return null; - } - // Case 1 - if (urlPath.startsWith(YOUTUBE_PATH_1)) { - return uri.getQueryParameter("v"); - } - // Case 2 - if (urlPath.startsWith(YOUTUBE_PATH_2)) { - return getVideoIdFromPath(YOUTUBE_PATH_2, urlPath); - } - // Case 3 - if (urlPath.startsWith(YOUTUBE_PATH_3)) { - return getVideoIdFromPath(YOUTUBE_PATH_3, urlPath); - } - // Case 4 - if (urlPath.startsWith(YOUTUBE_PATH_4)) { - return uri.getQueryParameter("video_id"); - } - // Case 5 - if (YOUTUBE_HOST_5.equalsIgnoreCase(uri.getHost())) { - return getVideoIdFromPath("/", urlPath); - } - return null; - } - - private static String getVideoIdFromPath(String prefixSubstring, String urlPath) { - return urlPath.substring(prefixSubstring.length()); - } - -} diff --git a/tests/src/com/android/messaging/util/YouTubeUtilTest.java b/tests/src/com/android/messaging/util/YouTubeUtilTest.java deleted file mode 100644 index 072d3e5df..000000000 --- a/tests/src/com/android/messaging/util/YouTubeUtilTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.messaging.util; - -import androidx.test.filters.SmallTest; - -import com.android.messaging.BugleTestCase; - -/* - * Class for testing YouTubeUtil. - */ -@SmallTest -public class YouTubeUtilTest extends BugleTestCase { - public void testGetYoutubePreviewImageLink() { - final String videoId = "dQw4w9WgXcQ"; - final String videoThumbnailUrl = YouTubeUtil.YOUTUBE_STATIC_THUMBNAIL_PREFIX + videoId - + YouTubeUtil.YOUTUBE_STATIC_THUMBNAIL_END; - - // Check known valid youtube links to videos - assertEquals( - YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/watch?v=" + videoId), - videoThumbnailUrl); - assertEquals( - YouTubeUtil.getYoutubePreviewImageLink("https://www.youtube.com/watch?v=" + videoId - + "&feature=youtu.be"), videoThumbnailUrl); - assertEquals( - YouTubeUtil.getYoutubePreviewImageLink("www.youtube.com/watch?v=" + videoId), - videoThumbnailUrl); - assertEquals( - YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/embed/" + videoId), - videoThumbnailUrl); - assertEquals(YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/v/" + videoId), - videoThumbnailUrl); - assertEquals( - YouTubeUtil.getYoutubePreviewImageLink("https://youtube.googleapis.com/v/" - + videoId), videoThumbnailUrl); - assertEquals( - YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/apiplayer?video_id=" - + videoId), videoThumbnailUrl); - // This is the type of links that are used as shares from YouTube and will be the most - // likely case that we see - assertEquals(YouTubeUtil.getYoutubePreviewImageLink("http://youtu.be/" + videoId), - videoThumbnailUrl); - - // Try links that shouldn't work - assertNull(YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com")); - } -} \ No newline at end of file