• 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.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