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