1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.intentresolver.contentpreview 18 19 import android.net.Uri 20 import android.view.LayoutInflater 21 import android.view.View 22 import android.view.ViewGroup 23 import android.widget.TextView 24 import androidx.annotation.IdRes 25 import androidx.test.ext.junit.runners.AndroidJUnit4 26 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation 27 import com.android.intentresolver.R 28 import com.android.intentresolver.widget.ActionRow 29 import com.google.common.truth.Truth.assertThat 30 import com.google.common.truth.Truth.assertWithMessage 31 import java.util.function.Consumer 32 import kotlin.coroutines.EmptyCoroutineContext 33 import kotlinx.coroutines.test.TestScope 34 import kotlinx.coroutines.test.UnconfinedTestDispatcher 35 import org.junit.Test 36 import org.junit.runner.RunWith 37 import org.mockito.kotlin.any 38 import org.mockito.kotlin.doReturn 39 import org.mockito.kotlin.mock 40 import org.mockito.kotlin.never 41 import org.mockito.kotlin.times 42 import org.mockito.kotlin.verify 43 44 private const val HEADLINE_IMAGES = "Image Headline" 45 private const val HEADLINE_VIDEOS = "Video Headline" 46 private const val HEADLINE_FILES = "Files Headline" 47 private const val SHARED_TEXT = "Some text to share" 48 49 @RunWith(AndroidJUnit4::class) 50 class FilesPlusTextContentPreviewUiTest { 51 private val testScope = TestScope(EmptyCoroutineContext + UnconfinedTestDispatcher()) 52 private val actionFactory = 53 object : ChooserContentPreviewUi.ActionFactory { 54 override fun getEditButtonRunnable(): Runnable? = null 55 56 override fun getCopyButtonRunnable(): Runnable? = null 57 58 override fun createCustomActions(): List<ActionRow.Action> = emptyList() 59 60 override fun getModifyShareAction(): ActionRow.Action? = null 61 62 override fun getExcludeSharedTextAction(): Consumer<Boolean> = Consumer<Boolean> {} 63 } 64 private val imageLoader = mock<ImageLoader>() 65 private val headlineGenerator = 66 mock<HeadlineGenerator> { 67 on { getImagesHeadline(any()) } doReturn HEADLINE_IMAGES 68 on { getVideosHeadline(any()) } doReturn HEADLINE_VIDEOS 69 on { getFilesHeadline(any()) } doReturn HEADLINE_FILES 70 } 71 private val testMetadataText: CharSequence = "Test metadata text" 72 73 private val context 74 get() = getInstrumentation().context 75 76 @Test 77 fun test_displayImagesPlusTextWithoutUriMetadataHeader_showImagesHeadline() { 78 val sharedFileCount = 2 79 val (previewView, headlineRow) = testLoadingHeadline("image/*", sharedFileCount) 80 81 assertWithMessage("Preview parent should not be null").that(previewView).isNotNull() 82 verify(headlineGenerator, times(1)).getImagesHeadline(sharedFileCount) 83 verifyPreviewHeadline(headlineRow, HEADLINE_IMAGES) 84 verifyPreviewMetadata(headlineRow, testMetadataText) 85 verifySharedText(previewView) 86 } 87 88 @Test 89 fun test_displayVideosPlusTextWithoutUriMetadataHeader_showVideosHeadline() { 90 val sharedFileCount = 2 91 val (previewView, headlineRow) = testLoadingHeadline("video/*", sharedFileCount) 92 93 verify(headlineGenerator, times(1)).getVideosHeadline(sharedFileCount) 94 assertWithMessage("Preview parent should not be null").that(previewView).isNotNull() 95 verifyPreviewHeadline(headlineRow, HEADLINE_VIDEOS) 96 verifyPreviewMetadata(headlineRow, testMetadataText) 97 verifySharedText(previewView) 98 } 99 100 @Test 101 fun test_displayDocsPlusTextWithoutUriMetadataHeader_showFilesHeadline() { 102 val sharedFileCount = 2 103 val (previewView, headlineRow) = testLoadingHeadline("application/pdf", sharedFileCount) 104 105 verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) 106 assertWithMessage("Preview parent should not be null").that(previewView).isNotNull() 107 verifyPreviewHeadline(headlineRow, HEADLINE_FILES) 108 verifyPreviewMetadata(headlineRow, testMetadataText) 109 verifySharedText(previewView) 110 } 111 112 @Test 113 fun test_displayMixedContentPlusTextWithoutUriMetadataHeader_showFilesHeadline() { 114 val sharedFileCount = 2 115 val (previewView, headlineRow) = testLoadingHeadline("*/*", sharedFileCount) 116 117 verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) 118 assertWithMessage("Preview parent should not be null").that(previewView).isNotNull() 119 verifyPreviewHeadline(headlineRow, HEADLINE_FILES) 120 verifyPreviewMetadata(headlineRow, testMetadataText) 121 verifySharedText(previewView) 122 } 123 124 @Test 125 fun test_displayImagesPlusTextWithUriMetadataSetHeader_showImagesHeadline() { 126 val loadedFileMetadata = createFileInfosWithMimeTypes("image/png", "image/jpeg") 127 val sharedFileCount = loadedFileMetadata.size 128 val (previewView, headlineRow) = 129 testLoadingHeadline("image/*", sharedFileCount, loadedFileMetadata) 130 131 verify(headlineGenerator, times(1)).getImagesHeadline(sharedFileCount) 132 assertWithMessage("Preview parent should not be null").that(previewView).isNotNull() 133 verifyPreviewHeadline(headlineRow, HEADLINE_IMAGES) 134 verifyPreviewMetadata(headlineRow, testMetadataText) 135 verifySharedText(previewView) 136 } 137 138 @Test 139 fun test_displayVideosPlusTextWithUriMetadataSetHeader_showVideosHeadline() { 140 val loadedFileMetadata = createFileInfosWithMimeTypes("video/mp4", "video/mp4") 141 val sharedFileCount = loadedFileMetadata.size 142 val (previewView, headlineRow) = 143 testLoadingHeadline("video/*", sharedFileCount, loadedFileMetadata) 144 145 verify(headlineGenerator, times(1)).getVideosHeadline(sharedFileCount) 146 assertWithMessage("Preview parent should not be null").that(previewView).isNotNull() 147 verifyPreviewHeadline(headlineRow, HEADLINE_VIDEOS) 148 verifyPreviewMetadata(headlineRow, testMetadataText) 149 verifySharedText(previewView) 150 } 151 152 @Test 153 fun test_displayImagesAndVideosPlusTextWithUriMetadataSetHeader_showFilesHeadline() { 154 val loadedFileMetadata = createFileInfosWithMimeTypes("image/png", "video/mp4") 155 val sharedFileCount = loadedFileMetadata.size 156 val (previewView, headlineRow) = 157 testLoadingHeadline("*/*", sharedFileCount, loadedFileMetadata) 158 159 verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) 160 assertWithMessage("Preview parent should not be null").that(previewView).isNotNull() 161 verifyPreviewHeadline(headlineRow, HEADLINE_FILES) 162 verifyPreviewMetadata(headlineRow, testMetadataText) 163 verifySharedText(previewView) 164 } 165 166 @Test 167 fun test_displayDocsPlusTextWithUriMetadataSetHeader_showFilesHeadline() { 168 val loadedFileMetadata = createFileInfosWithMimeTypes("application/pdf", "application/pdf") 169 val sharedFileCount = loadedFileMetadata.size 170 val (previewView, headlineRow) = 171 testLoadingHeadline("application/pdf", sharedFileCount, loadedFileMetadata) 172 173 verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) 174 assertWithMessage("Preview parent should not be null").that(previewView).isNotNull() 175 verifyPreviewHeadline(headlineRow, HEADLINE_FILES) 176 verifyPreviewMetadata(headlineRow, testMetadataText) 177 verifySharedText(previewView) 178 } 179 180 @Test 181 fun test_uriMetadataIsMoreSpecificThanIntentMimeType_headlineGetsUpdated() { 182 val sharedFileCount = 2 183 val testSubject = 184 FilesPlusTextContentPreviewUi( 185 testScope, 186 /*isSingleImage=*/ false, 187 sharedFileCount, 188 SHARED_TEXT, 189 /*intentMimeType=*/ "*/*", 190 actionFactory, 191 imageLoader, 192 DefaultMimeTypeClassifier, 193 headlineGenerator, 194 testMetadataText, 195 ) 196 val layoutInflater = LayoutInflater.from(context) 197 val gridLayout = 198 layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false) 199 as ViewGroup 200 val headlineRow = gridLayout.requireViewById<View>(R.id.chooser_headline_row_container) 201 202 testSubject.display( 203 context.resources, 204 LayoutInflater.from(context), 205 gridLayout, 206 headlineRow 207 ) 208 209 verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) 210 verify(headlineGenerator, never()).getImagesHeadline(sharedFileCount) 211 verifyPreviewHeadline(headlineRow, HEADLINE_FILES) 212 verifyPreviewMetadata(headlineRow, testMetadataText) 213 214 testSubject.updatePreviewMetadata(createFileInfosWithMimeTypes("image/png", "image/jpg")) 215 216 verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) 217 verify(headlineGenerator, times(1)).getImagesHeadline(sharedFileCount) 218 verifyPreviewHeadline(headlineRow, HEADLINE_IMAGES) 219 verifyPreviewMetadata(headlineRow, testMetadataText) 220 } 221 222 @Test 223 fun test_uriMetadataIsMoreSpecificThanIntentMimeTypeHeader_headlineGetsUpdated() { 224 val sharedFileCount = 2 225 val testSubject = 226 FilesPlusTextContentPreviewUi( 227 testScope, 228 /*isSingleImage=*/ false, 229 sharedFileCount, 230 SHARED_TEXT, 231 /*intentMimeType=*/ "*/*", 232 actionFactory, 233 imageLoader, 234 DefaultMimeTypeClassifier, 235 headlineGenerator, 236 testMetadataText, 237 ) 238 val layoutInflater = LayoutInflater.from(context) 239 val gridLayout = 240 layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false) 241 as ViewGroup 242 val headlineRow = gridLayout.requireViewById<View>(R.id.chooser_headline_row_container) 243 244 assertWithMessage("Headline should not be inflated by default") 245 .that(headlineRow.findViewById<View>(R.id.headline)) 246 .isNull() 247 assertWithMessage("Metadata should not be inflated by default") 248 .that(headlineRow.findViewById<View>(R.id.metadata)) 249 .isNull() 250 251 val previewView = 252 testSubject.display( 253 context.resources, 254 LayoutInflater.from(context), 255 gridLayout, 256 headlineRow 257 ) 258 259 verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) 260 verify(headlineGenerator, never()).getImagesHeadline(sharedFileCount) 261 assertWithMessage("Preview parent should not be null").that(previewView).isNotNull() 262 verifyPreviewHeadline(headlineRow, HEADLINE_FILES) 263 verifyPreviewMetadata(headlineRow, testMetadataText) 264 265 testSubject.updatePreviewMetadata(createFileInfosWithMimeTypes("image/png", "image/jpg")) 266 267 verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) 268 verify(headlineGenerator, times(1)).getImagesHeadline(sharedFileCount) 269 verifyPreviewHeadline(headlineRow, HEADLINE_IMAGES) 270 verifyPreviewMetadata(headlineRow, testMetadataText) 271 } 272 273 private fun testLoadingHeadline( 274 intentMimeType: String, 275 sharedFileCount: Int, 276 loadedFileMetadata: List<FileInfo>? = null, 277 ): Pair<ViewGroup?, View> { 278 val testSubject = 279 FilesPlusTextContentPreviewUi( 280 testScope, 281 /*isSingleImage=*/ false, 282 sharedFileCount, 283 SHARED_TEXT, 284 intentMimeType, 285 actionFactory, 286 imageLoader, 287 DefaultMimeTypeClassifier, 288 headlineGenerator, 289 testMetadataText, 290 ) 291 val layoutInflater = LayoutInflater.from(context) 292 val gridLayout = 293 layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false) 294 as ViewGroup 295 val headlineRow = gridLayout.requireViewById<View>(R.id.chooser_headline_row_container) 296 297 assertWithMessage("Headline should not be inflated by default") 298 .that(headlineRow.findViewById<View>(R.id.headline)) 299 .isNull() 300 301 assertWithMessage("Metadata should not be inflated by default") 302 .that(headlineRow.findViewById<View>(R.id.metadata)) 303 .isNull() 304 305 loadedFileMetadata?.let(testSubject::updatePreviewMetadata) 306 return testSubject.display( 307 context.resources, 308 LayoutInflater.from(context), 309 gridLayout, 310 headlineRow 311 ) to headlineRow 312 } 313 314 private fun createFileInfosWithMimeTypes(vararg mimeTypes: String): List<FileInfo> { 315 val uri = Uri.parse("content://pkg.app/file") 316 return mimeTypes.map { mimeType -> FileInfo.Builder(uri).withMimeType(mimeType).build() } 317 } 318 319 private fun verifyTextViewText( 320 parentView: View?, 321 @IdRes textViewResId: Int, 322 expectedText: CharSequence, 323 ) { 324 assertThat(parentView).isNotNull() 325 val textView = parentView?.findViewById<TextView>(textViewResId) 326 assertThat(textView).isNotNull() 327 assertThat(textView?.text).isEqualTo(expectedText) 328 } 329 330 private fun verifyPreviewHeadline(headerViewParent: View?, expectedText: String) { 331 verifyTextViewText(headerViewParent, R.id.headline, expectedText) 332 } 333 334 private fun verifyPreviewMetadata(headerViewParent: View?, expectedText: CharSequence) { 335 verifyTextViewText(headerViewParent, R.id.metadata, expectedText) 336 } 337 338 private fun verifySharedText(previewView: ViewGroup?) { 339 verifyTextViewText(previewView, R.id.content_preview_text, SHARED_TEXT) 340 } 341 } 342