• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.CheckBox
24 import android.widget.TextView
25 import androidx.annotation.IdRes
26 import androidx.test.ext.junit.runners.AndroidJUnit4
27 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
28 import com.android.intentresolver.R
29 import com.android.intentresolver.widget.ActionRow
30 import com.google.common.truth.Truth.assertThat
31 import com.google.common.truth.Truth.assertWithMessage
32 import java.util.function.Consumer
33 import kotlin.coroutines.EmptyCoroutineContext
34 import kotlinx.coroutines.test.TestScope
35 import kotlinx.coroutines.test.UnconfinedTestDispatcher
36 import org.junit.Test
37 import org.junit.runner.RunWith
38 import org.mockito.kotlin.any
39 import org.mockito.kotlin.doReturn
40 import org.mockito.kotlin.mock
41 import org.mockito.kotlin.never
42 import org.mockito.kotlin.times
43 import org.mockito.kotlin.verify
44 
45 private const val HEADLINE_IMAGES = "Image Headline"
46 private const val HEADLINE_VIDEOS = "Video Headline"
47 private const val HEADLINE_FILES = "Files Headline"
48 private const val SHARED_TEXT = "Some text to share"
49 
50 @RunWith(AndroidJUnit4::class)
51 class FilesPlusTextContentPreviewUiTest {
52     private val testScope = TestScope(EmptyCoroutineContext + UnconfinedTestDispatcher())
53     private val actionFactory =
54         object : ChooserContentPreviewUi.ActionFactory {
55             override fun getEditButtonRunnable(): Runnable? = null
56 
57             override fun getCopyButtonRunnable(): Runnable? = null
58 
59             override fun createCustomActions(): List<ActionRow.Action> = emptyList()
60 
61             override fun getModifyShareAction(): ActionRow.Action? = null
62 
63             override fun getExcludeSharedTextAction(): Consumer<Boolean> = Consumer<Boolean> {}
64         }
65     private val imageLoader = mock<ImageLoader>()
66     private val headlineGenerator =
67         mock<HeadlineGenerator> {
68             on { getImagesHeadline(any()) } doReturn HEADLINE_IMAGES
69             on { getVideosHeadline(any()) } doReturn HEADLINE_VIDEOS
70             on { getFilesHeadline(any()) } doReturn HEADLINE_FILES
71         }
72     private val testMetadataText: CharSequence = "Test metadata text"
73 
74     private val context
75         get() = getInstrumentation().context
76 
77     @Test
78     fun test_displayImagesPlusTextWithoutUriMetadataHeader_showImagesHeadline() {
79         val sharedFileCount = 2
80         val (previewView, headlineRow) = testLoadingHeadline("image/*", sharedFileCount)
81 
82         assertWithMessage("Preview parent should not be null").that(previewView).isNotNull()
83         verify(headlineGenerator, times(1)).getImagesHeadline(sharedFileCount)
84         verifyPreviewHeadline(headlineRow, HEADLINE_IMAGES)
85         verifyPreviewMetadata(headlineRow, testMetadataText)
86         verifySharedText(previewView)
87     }
88 
89     @Test
90     fun test_displayVideosPlusTextWithoutUriMetadataHeader_showVideosHeadline() {
91         val sharedFileCount = 2
92         val (previewView, headlineRow) = testLoadingHeadline("video/*", sharedFileCount)
93 
94         verify(headlineGenerator, times(1)).getVideosHeadline(sharedFileCount)
95         assertWithMessage("Preview parent should not be null").that(previewView).isNotNull()
96         verifyPreviewHeadline(headlineRow, HEADLINE_VIDEOS)
97         verifyPreviewMetadata(headlineRow, testMetadataText)
98         verifySharedText(previewView)
99     }
100 
101     @Test
102     fun test_displayDocsPlusTextWithoutUriMetadataHeader_showFilesHeadline() {
103         val sharedFileCount = 2
104         val (previewView, headlineRow) = testLoadingHeadline("application/pdf", sharedFileCount)
105 
106         verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount)
107         assertWithMessage("Preview parent should not be null").that(previewView).isNotNull()
108         verifyPreviewHeadline(headlineRow, HEADLINE_FILES)
109         verifyPreviewMetadata(headlineRow, testMetadataText)
110         verifySharedText(previewView)
111     }
112 
113     @Test
114     fun test_displayMixedContentPlusTextWithoutUriMetadataHeader_showFilesHeadline() {
115         val sharedFileCount = 2
116         val (previewView, headlineRow) = testLoadingHeadline("*/*", sharedFileCount)
117 
118         verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount)
119         assertWithMessage("Preview parent should not be null").that(previewView).isNotNull()
120         verifyPreviewHeadline(headlineRow, HEADLINE_FILES)
121         verifyPreviewMetadata(headlineRow, testMetadataText)
122         verifySharedText(previewView)
123     }
124 
125     @Test
126     fun test_displayImagesPlusTextWithUriMetadataSetHeader_showImagesHeadline() {
127         val loadedFileMetadata = createFileInfosWithMimeTypes("image/png", "image/jpeg")
128         val sharedFileCount = loadedFileMetadata.size
129         val (previewView, headlineRow) =
130             testLoadingHeadline("image/*", sharedFileCount, loadedFileMetadata)
131 
132         verify(headlineGenerator, times(1)).getImagesHeadline(sharedFileCount)
133         assertWithMessage("Preview parent should not be null").that(previewView).isNotNull()
134         verifyPreviewHeadline(headlineRow, HEADLINE_IMAGES)
135         verifyPreviewMetadata(headlineRow, testMetadataText)
136         verifySharedText(previewView)
137     }
138 
139     @Test
140     fun test_displayVideosPlusTextWithUriMetadataSetHeader_showVideosHeadline() {
141         val loadedFileMetadata = createFileInfosWithMimeTypes("video/mp4", "video/mp4")
142         val sharedFileCount = loadedFileMetadata.size
143         val (previewView, headlineRow) =
144             testLoadingHeadline("video/*", sharedFileCount, loadedFileMetadata)
145 
146         verify(headlineGenerator, times(1)).getVideosHeadline(sharedFileCount)
147         assertWithMessage("Preview parent should not be null").that(previewView).isNotNull()
148         verifyPreviewHeadline(headlineRow, HEADLINE_VIDEOS)
149         verifyPreviewMetadata(headlineRow, testMetadataText)
150         verifySharedText(previewView)
151     }
152 
153     @Test
154     fun test_displayImagesAndVideosPlusTextWithUriMetadataSetHeader_showFilesHeadline() {
155         val loadedFileMetadata = createFileInfosWithMimeTypes("image/png", "video/mp4")
156         val sharedFileCount = loadedFileMetadata.size
157         val (previewView, headlineRow) =
158             testLoadingHeadline("*/*", sharedFileCount, loadedFileMetadata)
159 
160         verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount)
161         assertWithMessage("Preview parent should not be null").that(previewView).isNotNull()
162         verifyPreviewHeadline(headlineRow, HEADLINE_FILES)
163         verifyPreviewMetadata(headlineRow, testMetadataText)
164         verifySharedText(previewView)
165     }
166 
167     @Test
168     fun test_displayDocsPlusTextWithUriMetadataSetHeader_showFilesHeadline() {
169         val loadedFileMetadata = createFileInfosWithMimeTypes("application/pdf", "application/pdf")
170         val sharedFileCount = loadedFileMetadata.size
171         val (previewView, headlineRow) =
172             testLoadingHeadline("application/pdf", sharedFileCount, loadedFileMetadata)
173 
174         verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount)
175         assertWithMessage("Preview parent should not be null").that(previewView).isNotNull()
176         verifyPreviewHeadline(headlineRow, HEADLINE_FILES)
177         verifyPreviewMetadata(headlineRow, testMetadataText)
178         verifySharedText(previewView)
179     }
180 
181     @Test
182     fun test_uriMetadataIsMoreSpecificThanIntentMimeType_headlineGetsUpdated() {
183         val sharedFileCount = 2
184         val testSubject =
185             FilesPlusTextContentPreviewUi(
186                 testScope,
187                 /*isSingleImage=*/ false,
188                 sharedFileCount,
189                 SHARED_TEXT,
190                 /*intentMimeType=*/ "*/*",
191                 actionFactory,
192                 imageLoader,
193                 DefaultMimeTypeClassifier,
194                 headlineGenerator,
195                 testMetadataText,
196                 /* allowTextToggle=*/ false,
197             )
198         val layoutInflater = LayoutInflater.from(context)
199         val gridLayout =
200             layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false)
201                 as ViewGroup
202         val headlineRow = gridLayout.requireViewById<View>(R.id.chooser_headline_row_container)
203 
204         testSubject.display(
205             context.resources,
206             LayoutInflater.from(context),
207             gridLayout,
208             headlineRow,
209         )
210 
211         verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount)
212         verify(headlineGenerator, never()).getImagesHeadline(sharedFileCount)
213         verifyPreviewHeadline(headlineRow, HEADLINE_FILES)
214         verifyPreviewMetadata(headlineRow, testMetadataText)
215 
216         testSubject.updatePreviewMetadata(createFileInfosWithMimeTypes("image/png", "image/jpg"))
217 
218         verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount)
219         verify(headlineGenerator, times(1)).getImagesHeadline(sharedFileCount)
220         verifyPreviewHeadline(headlineRow, HEADLINE_IMAGES)
221         verifyPreviewMetadata(headlineRow, testMetadataText)
222     }
223 
224     @Test
225     fun test_uriMetadataIsMoreSpecificThanIntentMimeTypeHeader_headlineGetsUpdated() {
226         val sharedFileCount = 2
227         val testSubject =
228             FilesPlusTextContentPreviewUi(
229                 testScope,
230                 /*isSingleImage=*/ false,
231                 sharedFileCount,
232                 SHARED_TEXT,
233                 /*intentMimeType=*/ "*/*",
234                 actionFactory,
235                 imageLoader,
236                 DefaultMimeTypeClassifier,
237                 headlineGenerator,
238                 testMetadataText,
239                 /* allowTextToggle=*/ false,
240             )
241         val layoutInflater = LayoutInflater.from(context)
242         val gridLayout =
243             layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false)
244                 as ViewGroup
245         val headlineRow = gridLayout.requireViewById<View>(R.id.chooser_headline_row_container)
246 
247         assertWithMessage("Headline should not be inflated by default")
248             .that(headlineRow.findViewById<View>(R.id.headline))
249             .isNull()
250         assertWithMessage("Metadata should not be inflated by default")
251             .that(headlineRow.findViewById<View>(R.id.metadata))
252             .isNull()
253 
254         val previewView =
255             testSubject.display(
256                 context.resources,
257                 LayoutInflater.from(context),
258                 gridLayout,
259                 headlineRow,
260             )
261 
262         verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount)
263         verify(headlineGenerator, never()).getImagesHeadline(sharedFileCount)
264         assertWithMessage("Preview parent should not be null").that(previewView).isNotNull()
265         verifyPreviewHeadline(headlineRow, HEADLINE_FILES)
266         verifyPreviewMetadata(headlineRow, testMetadataText)
267 
268         testSubject.updatePreviewMetadata(createFileInfosWithMimeTypes("image/png", "image/jpg"))
269 
270         verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount)
271         verify(headlineGenerator, times(1)).getImagesHeadline(sharedFileCount)
272         verifyPreviewHeadline(headlineRow, HEADLINE_IMAGES)
273         verifyPreviewMetadata(headlineRow, testMetadataText)
274     }
275 
276     @Test
277     fun test_allowToggle() {
278         val testSubject =
279             FilesPlusTextContentPreviewUi(
280                 testScope,
281                 /*isSingleImage=*/ false,
282                 /* fileCount=*/ 1,
283                 SHARED_TEXT,
284                 /*intentMimeType=*/ "*/*",
285                 actionFactory,
286                 imageLoader,
287                 DefaultMimeTypeClassifier,
288                 headlineGenerator,
289                 testMetadataText,
290                 /* allowTextToggle=*/ true,
291             )
292         val layoutInflater = LayoutInflater.from(context)
293         val gridLayout =
294             layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false)
295                 as ViewGroup
296         val headlineRow = gridLayout.requireViewById<View>(R.id.chooser_headline_row_container)
297 
298         testSubject.display(
299             context.resources,
300             LayoutInflater.from(context),
301             gridLayout,
302             headlineRow,
303         )
304 
305         val checkbox = headlineRow.requireViewById<CheckBox>(R.id.include_text_action)
306         assertThat(checkbox.visibility).isEqualTo(View.VISIBLE)
307         assertThat(checkbox.isChecked).isTrue()
308     }
309 
310     @Test
311     fun test_hideTextToggle() {
312         val testSubject =
313             FilesPlusTextContentPreviewUi(
314                 testScope,
315                 /*isSingleImage=*/ false,
316                 /* fileCount=*/ 1,
317                 SHARED_TEXT,
318                 /*intentMimeType=*/ "*/*",
319                 actionFactory,
320                 imageLoader,
321                 DefaultMimeTypeClassifier,
322                 headlineGenerator,
323                 testMetadataText,
324                 /* allowTextToggle=*/ false,
325             )
326         val layoutInflater = LayoutInflater.from(context)
327         val gridLayout =
328             layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false)
329                 as ViewGroup
330         val headlineRow = gridLayout.requireViewById<View>(R.id.chooser_headline_row_container)
331 
332         testSubject.display(
333             context.resources,
334             LayoutInflater.from(context),
335             gridLayout,
336             headlineRow,
337         )
338 
339         val checkbox = headlineRow.requireViewById<CheckBox>(R.id.include_text_action)
340         assertThat(checkbox.visibility).isNotEqualTo(View.VISIBLE)
341     }
342 
343     private fun testLoadingHeadline(
344         intentMimeType: String,
345         sharedFileCount: Int,
346         loadedFileMetadata: List<FileInfo>? = null,
347     ): Pair<ViewGroup?, View> {
348         val testSubject =
349             FilesPlusTextContentPreviewUi(
350                 testScope,
351                 /*isSingleImage=*/ false,
352                 sharedFileCount,
353                 SHARED_TEXT,
354                 intentMimeType,
355                 actionFactory,
356                 imageLoader,
357                 DefaultMimeTypeClassifier,
358                 headlineGenerator,
359                 testMetadataText,
360                 /* allowTextToggle=*/ false,
361             )
362         val layoutInflater = LayoutInflater.from(context)
363         val gridLayout =
364             layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false)
365                 as ViewGroup
366         val headlineRow = gridLayout.requireViewById<View>(R.id.chooser_headline_row_container)
367 
368         assertWithMessage("Headline should not be inflated by default")
369             .that(headlineRow.findViewById<View>(R.id.headline))
370             .isNull()
371 
372         assertWithMessage("Metadata should not be inflated by default")
373             .that(headlineRow.findViewById<View>(R.id.metadata))
374             .isNull()
375 
376         loadedFileMetadata?.let(testSubject::updatePreviewMetadata)
377         return testSubject.display(
378             context.resources,
379             LayoutInflater.from(context),
380             gridLayout,
381             headlineRow,
382         ) to headlineRow
383     }
384 
385     private fun createFileInfosWithMimeTypes(vararg mimeTypes: String): List<FileInfo> {
386         val uri = Uri.parse("content://pkg.app/file")
387         return mimeTypes.map { mimeType -> FileInfo.Builder(uri).withMimeType(mimeType).build() }
388     }
389 
390     private fun verifyTextViewText(
391         parentView: View?,
392         @IdRes textViewResId: Int,
393         expectedText: CharSequence,
394     ) {
395         assertThat(parentView).isNotNull()
396         val textView = parentView?.findViewById<TextView>(textViewResId)
397         assertThat(textView).isNotNull()
398         assertThat(textView?.text).isEqualTo(expectedText)
399     }
400 
401     private fun verifyPreviewHeadline(headerViewParent: View?, expectedText: String) {
402         verifyTextViewText(headerViewParent, R.id.headline, expectedText)
403     }
404 
405     private fun verifyPreviewMetadata(headerViewParent: View?, expectedText: CharSequence) {
406         verifyTextViewText(headerViewParent, R.id.metadata, expectedText)
407     }
408 
409     private fun verifySharedText(previewView: ViewGroup?) {
410         verifyTextViewText(previewView, R.id.content_preview_text, SHARED_TEXT)
411     }
412 }
413