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.ImagePreviewView.TransitionElementStatusCallback 29 import com.google.common.truth.Truth.assertThat 30 import com.google.common.truth.Truth.assertWithMessage 31 import kotlin.coroutines.EmptyCoroutineContext 32 import kotlinx.coroutines.ExperimentalCoroutinesApi 33 import kotlinx.coroutines.flow.MutableSharedFlow 34 import kotlinx.coroutines.flow.asFlow 35 import kotlinx.coroutines.flow.takeWhile 36 import kotlinx.coroutines.test.TestScope 37 import kotlinx.coroutines.test.UnconfinedTestDispatcher 38 import kotlinx.coroutines.test.runTest 39 import org.junit.Test 40 import org.junit.runner.RunWith 41 import org.mockito.kotlin.any 42 import org.mockito.kotlin.doReturn 43 import org.mockito.kotlin.mock 44 import org.mockito.kotlin.times 45 import org.mockito.kotlin.verify 46 47 private const val IMAGE_HEADLINE = "Image Headline" 48 private const val VIDEO_HEADLINE = "Video Headline" 49 private const val FILES_HEADLINE = "Files Headline" 50 51 @RunWith(AndroidJUnit4::class) 52 class UnifiedContentPreviewUiTest { 53 @OptIn(ExperimentalCoroutinesApi::class) 54 private val testScope = TestScope(EmptyCoroutineContext + UnconfinedTestDispatcher()) 55 private val actionFactory = 56 mock<ChooserContentPreviewUi.ActionFactory> { 57 on { createCustomActions() } doReturn emptyList() 58 } 59 private val imageLoader = mock<ImageLoader>() 60 private val headlineGenerator = 61 mock<HeadlineGenerator> { 62 on { getImagesHeadline(any()) } doReturn IMAGE_HEADLINE 63 on { getVideosHeadline(any()) } doReturn VIDEO_HEADLINE 64 on { getFilesHeadline(any()) } doReturn FILES_HEADLINE 65 } 66 private val testMetadataText: CharSequence = "Test metadata text" 67 68 private val context 69 get() = getInstrumentation().context 70 71 @Test 72 fun test_displayImagesWithoutUriMetadataHeader_showImagesHeadline() { 73 testLoadingHeadline("image/*", files = null) { headlineRow -> 74 verify(headlineGenerator, times(1)).getImagesHeadline(2) 75 verifyPreviewHeadline(headlineRow, IMAGE_HEADLINE) 76 verifyPreviewMetadata(headlineRow, testMetadataText) 77 } 78 } 79 80 @Test 81 fun test_displayVideosWithoutUriMetadataHeader_showImagesHeadline() { 82 testLoadingHeadline("video/*", files = null) { headlineRow -> 83 verify(headlineGenerator, times(1)).getVideosHeadline(2) 84 verifyPreviewHeadline(headlineRow, VIDEO_HEADLINE) 85 verifyPreviewMetadata(headlineRow, testMetadataText) 86 } 87 } 88 89 @Test 90 fun test_displayDocumentsWithoutUriMetadataHeader_showImagesHeadline() { 91 testLoadingHeadline("application/pdf", files = null) { headlineRow -> 92 verify(headlineGenerator, times(1)).getFilesHeadline(2) 93 verifyPreviewHeadline(headlineRow, FILES_HEADLINE) 94 verifyPreviewMetadata(headlineRow, testMetadataText) 95 } 96 } 97 98 @Test 99 fun test_displayMixedContentWithoutUriMetadataHeader_showImagesHeadline() { 100 testLoadingHeadline("*/*", files = null) { headlineRow -> 101 verify(headlineGenerator, times(1)).getFilesHeadline(2) 102 verifyPreviewHeadline(headlineRow, FILES_HEADLINE) 103 verifyPreviewMetadata(headlineRow, testMetadataText) 104 } 105 } 106 107 @Test 108 fun test_displayImagesWithUriMetadataSetHeader_showImagesHeadline() { 109 val uri = Uri.parse("content://pkg.app/image.png") 110 val files = 111 listOf( 112 FileInfo.Builder(uri).withMimeType("image/png").build(), 113 FileInfo.Builder(uri).withMimeType("image/jpeg").build(), 114 ) 115 testLoadingHeadline("image/*", files) { headlineRow -> 116 verify(headlineGenerator, times(1)).getImagesHeadline(2) 117 verifyPreviewHeadline(headlineRow, IMAGE_HEADLINE) 118 } 119 } 120 121 @Test 122 fun test_displayVideosWithUriMetadataSetHeader_showImagesHeadline() { 123 val uri = Uri.parse("content://pkg.app/image.png") 124 val files = 125 listOf( 126 FileInfo.Builder(uri).withMimeType("video/mp4").build(), 127 FileInfo.Builder(uri).withMimeType("video/mp4").build(), 128 ) 129 testLoadingHeadline("video/*", files) { headlineRow -> 130 verify(headlineGenerator, times(1)).getVideosHeadline(2) 131 verifyPreviewHeadline(headlineRow, VIDEO_HEADLINE) 132 } 133 } 134 135 @Test 136 fun test_displayImagesAndVideosWithUriMetadataSetHeader_showImagesHeadline() { 137 val uri = Uri.parse("content://pkg.app/image.png") 138 val files = 139 listOf( 140 FileInfo.Builder(uri).withMimeType("image/png").build(), 141 FileInfo.Builder(uri).withMimeType("video/mp4").build(), 142 ) 143 testLoadingHeadline("*/*", files) { headlineRow -> 144 verify(headlineGenerator, times(1)).getFilesHeadline(2) 145 verifyPreviewHeadline(headlineRow, FILES_HEADLINE) 146 } 147 } 148 149 @Test 150 fun test_displayDocumentsWithUriMetadataSetHeader_showImagesHeadline() { 151 val uri = Uri.parse("content://pkg.app/image.png") 152 val files = 153 listOf( 154 FileInfo.Builder(uri).withMimeType("application/pdf").build(), 155 FileInfo.Builder(uri).withMimeType("application/pdf").build(), 156 ) 157 testLoadingHeadline("application/pdf", files) { headlineRow -> 158 verify(headlineGenerator, times(1)).getFilesHeadline(2) 159 verifyPreviewHeadline(headlineRow, FILES_HEADLINE) 160 } 161 } 162 163 private fun testLoadingHeadline( 164 intentMimeType: String, 165 files: List<FileInfo>?, 166 verificationBlock: (View?) -> Unit, 167 ) { 168 testScope.runTest { 169 val endMarker = FileInfo.Builder(Uri.EMPTY).build() 170 val emptySourceFlow = MutableSharedFlow<FileInfo>(replay = 1) 171 val testSubject = 172 UnifiedContentPreviewUi( 173 testScope, 174 /*isSingleImage=*/ false, 175 intentMimeType, 176 actionFactory, 177 imageLoader, 178 DefaultMimeTypeClassifier, 179 object : TransitionElementStatusCallback { 180 override fun onTransitionElementReady(name: String) = Unit 181 override fun onAllTransitionElementsReady() = Unit 182 }, 183 files?.let { it.asFlow() } ?: emptySourceFlow.takeWhile { it !== endMarker }, 184 /*itemCount=*/ 2, 185 headlineGenerator, 186 testMetadataText, 187 ) 188 val layoutInflater = LayoutInflater.from(context) 189 val gridLayout = 190 layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false) 191 as ViewGroup 192 val headlineRow = gridLayout.requireViewById<View>(R.id.chooser_headline_row_container) 193 194 assertWithMessage("Headline row should not be inflated by default") 195 .that(headlineRow.findViewById<View>(R.id.headline)) 196 .isNull() 197 198 testSubject.display( 199 context.resources, 200 LayoutInflater.from(context), 201 gridLayout, 202 headlineRow, 203 ) 204 emptySourceFlow.tryEmit(endMarker) 205 verificationBlock(headlineRow) 206 } 207 } 208 209 private fun verifyTextViewText( 210 viewParent: View?, 211 @IdRes textViewResId: Int, 212 expectedText: CharSequence, 213 ) { 214 assertThat(viewParent).isNotNull() 215 val textView = viewParent?.findViewById<TextView>(textViewResId) 216 assertThat(textView).isNotNull() 217 assertThat(textView?.text).isEqualTo(expectedText) 218 } 219 220 private fun verifyPreviewHeadline(headerViewParent: View?, expectedText: String) { 221 verifyTextViewText(headerViewParent, R.id.headline, expectedText) 222 } 223 224 private fun verifyPreviewMetadata(headerViewParent: View?, expectedText: CharSequence) { 225 verifyTextViewText(headerViewParent, R.id.metadata, expectedText) 226 } 227 } 228