1 /*
2  * Copyright (C) 2024 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.photopicker.features.privacyexplainer
18 
19 import android.content.ContentProvider
20 import android.content.ContentResolver
21 import android.content.Context
22 import android.content.pm.PackageManager
23 import android.os.UserManager
24 import android.test.mock.MockContentResolver
25 import androidx.compose.ui.test.ExperimentalTestApi
26 import androidx.compose.ui.test.assertIsDisplayed
27 import androidx.compose.ui.test.assertIsNotDisplayed
28 import androidx.compose.ui.test.hasText
29 import androidx.compose.ui.test.junit4.createAndroidComposeRule
30 import com.android.photopicker.R
31 import com.android.photopicker.core.ActivityModule
32 import com.android.photopicker.core.ApplicationModule
33 import com.android.photopicker.core.ApplicationOwned
34 import com.android.photopicker.core.Background
35 import com.android.photopicker.core.ConcurrencyModule
36 import com.android.photopicker.core.EmbeddedServiceModule
37 import com.android.photopicker.core.Main
38 import com.android.photopicker.core.ViewModelModule
39 import com.android.photopicker.core.banners.BannerDefinitions
40 import com.android.photopicker.core.banners.BannerManager
41 import com.android.photopicker.core.banners.BannerState
42 import com.android.photopicker.core.banners.BannerStateDao
43 import com.android.photopicker.core.configuration.ConfigurationManager
44 import com.android.photopicker.core.database.DatabaseManager
45 import com.android.photopicker.core.events.Events
46 import com.android.photopicker.core.features.FeatureManager
47 import com.android.photopicker.core.glide.GlideTestRule
48 import com.android.photopicker.core.selection.Selection
49 import com.android.photopicker.data.model.Media
50 import com.android.photopicker.features.PhotopickerFeatureBaseTest
51 import com.android.photopicker.inject.PhotopickerTestModule
52 import com.android.photopicker.tests.HiltTestActivity
53 import com.android.photopicker.util.test.MockContentProviderWrapper
54 import com.android.photopicker.util.test.nonNullableEq
55 import com.android.photopicker.util.test.whenever
56 import dagger.Lazy
57 import dagger.Module
58 import dagger.hilt.InstallIn
59 import dagger.hilt.android.testing.BindValue
60 import dagger.hilt.android.testing.HiltAndroidRule
61 import dagger.hilt.android.testing.HiltAndroidTest
62 import dagger.hilt.android.testing.UninstallModules
63 import dagger.hilt.components.SingletonComponent
64 import javax.inject.Inject
65 import kotlinx.coroutines.CoroutineDispatcher
66 import kotlinx.coroutines.CoroutineScope
67 import kotlinx.coroutines.ExperimentalCoroutinesApi
68 import kotlinx.coroutines.test.StandardTestDispatcher
69 import kotlinx.coroutines.test.TestScope
70 import kotlinx.coroutines.test.advanceTimeBy
71 import kotlinx.coroutines.test.runTest
72 import org.junit.Before
73 import org.junit.Rule
74 import org.junit.Test
75 import org.mockito.Mock
76 import org.mockito.Mockito.any
77 import org.mockito.Mockito.anyInt
78 import org.mockito.Mockito.anyString
79 import org.mockito.MockitoAnnotations
80 
81 @UninstallModules(
82     ActivityModule::class,
83     ApplicationModule::class,
84     ConcurrencyModule::class,
85     EmbeddedServiceModule::class,
86     ViewModelModule::class,
87 )
88 @HiltAndroidTest
89 @OptIn(ExperimentalCoroutinesApi::class, ExperimentalTestApi::class)
90 class PrivacyExplainerFeatureTest : PhotopickerFeatureBaseTest() {
91 
92     /* Hilt's rule needs to come first to ensure the DI container is setup for the test. */
93     @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
94     @get:Rule(order = 1)
95     val composeTestRule = createAndroidComposeRule(activityClass = HiltTestActivity::class.java)
96     @get:Rule(order = 2) val glideRule = GlideTestRule()
97 
98     /* Setup dependencies for the UninstallModules for the test class. */
99     @Module @InstallIn(SingletonComponent::class) class TestModule : PhotopickerTestModule()
100 
101     val testDispatcher = StandardTestDispatcher()
102 
103     /* Overrides for ActivityModule */
104     val testScope: TestScope = TestScope(testDispatcher)
105     @BindValue @Main val mainScope: CoroutineScope = testScope
106     @BindValue @Background var testBackgroundScope: CoroutineScope = testScope.backgroundScope
107 
108     /* Overrides for ViewModelModule */
109     @BindValue val viewModelScopeOverride: CoroutineScope? = testScope.backgroundScope
110 
111     /* Overrides for the ConcurrencyModule */
112     @BindValue @Main val mainDispatcher: CoroutineDispatcher = testDispatcher
113     @BindValue @Background val backgroundDispatcher: CoroutineDispatcher = testDispatcher
114 
115     /**
116      * Preview uses Glide for loading images, so we have to mock out the dependencies for Glide
117      * Replace the injected ContentResolver binding in [ApplicationModule] with this test value.
118      */
119     @BindValue @ApplicationOwned lateinit var contentResolver: ContentResolver
120     private lateinit var provider: MockContentProviderWrapper
121     @Mock lateinit var mockContentProvider: ContentProvider
122 
123     // Needed for UserMonitor
124     @Mock lateinit var mockUserManager: UserManager
125     @Mock lateinit var mockPackageManager: PackageManager
126 
127     @Inject lateinit var mockContext: Context
128     @Inject lateinit var selection: Selection<Media>
129     @Inject lateinit var featureManager: FeatureManager
130     @Inject lateinit var events: Events
131     @Inject lateinit var bannerManager: Lazy<BannerManager>
132     @Inject lateinit var databaseManager: DatabaseManager
133     @Inject override lateinit var configurationManager: Lazy<ConfigurationManager>
134 
135     @Before
setupnull136     fun setup() {
137         MockitoAnnotations.initMocks(this)
138 
139         hiltRule.inject()
140 
141         // Stub for MockContentResolver constructor
142         whenever(mockContext.getApplicationInfo()) { getTestableContext().getApplicationInfo() }
143 
144         // Stub out the content resolver for Glide
145         val mockContentResolver = MockContentResolver(mockContext)
146         provider = MockContentProviderWrapper(mockContentProvider)
147         mockContentResolver.addProvider(MockContentProviderWrapper.AUTHORITY, provider)
148         contentResolver = mockContentResolver
149 
150         // Return a resource png so that glide actually has something to load
151         whenever(mockContentProvider.openTypedAssetFile(any(), any(), any(), any())) {
152             getTestableContext().getResources().openRawResourceFd(R.drawable.android)
153         }
154         setupTestForUserMonitor(mockContext, mockUserManager, contentResolver, mockPackageManager)
155     }
156 
157     @Test
testPrivacyExplainerBannerIsShownnull158     fun testPrivacyExplainerBannerIsShown() =
159         testScope.runTest {
160             val bannerStateDao = databaseManager.acquireDao(BannerStateDao::class.java)
161             whenever(bannerStateDao.getBannerState(anyString(), anyInt())) { null }
162 
163             configurationManager
164                 .get()
165                 .setCaller(
166                     callingPackage = "com.android.test.package",
167                     callingPackageUid = 12345,
168                     callingPackageLabel = "Test Package",
169                 )
170             advanceTimeBy(100)
171 
172             val resources = getTestableContext().getResources()
173             val expectedPrivacyMessage =
174                 resources.getString(R.string.photopicker_privacy_explainer, "Test Package")
175 
176             bannerManager.get().refreshBanners()
177             advanceTimeBy(100)
178 
179             composeTestRule.setContent {
180                 callPhotopickerMain(
181                     featureManager = featureManager,
182                     selection = selection,
183                     events = events,
184                 )
185             }
186             composeTestRule.waitForIdle()
187             composeTestRule.onNode(hasText(expectedPrivacyMessage)).assertIsDisplayed()
188         }
189 
190     @Test
testPrivacyExplainerBannerIsHiddenWhenDismissednull191     fun testPrivacyExplainerBannerIsHiddenWhenDismissed() =
192         testScope.runTest {
193             val bannerStateDao = databaseManager.acquireDao(BannerStateDao::class.java)
194 
195             whenever(bannerStateDao.getBannerState(anyString(), anyInt())) { null }
196             whenever(
197                 bannerStateDao.getBannerState(
198                     nonNullableEq(BannerDefinitions.PRIVACY_EXPLAINER.id),
199                     anyInt(),
200                 )
201             ) {
202                 BannerState(
203                     bannerId = BannerDefinitions.PRIVACY_EXPLAINER.id,
204                     dismissed = true,
205                     uid = 12345,
206                 )
207             }
208             // Mock out database state with previously dismissed state.
209             configurationManager
210                 .get()
211                 .setCaller(
212                     callingPackage = "com.android.test.package",
213                     callingPackageUid = 12345,
214                     callingPackageLabel = "Test Package",
215                 )
216             advanceTimeBy(1000)
217             val resources = getTestableContext().getResources()
218             val expectedPrivacyMessage =
219                 resources.getString(R.string.photopicker_privacy_explainer, "Test Package")
220 
221             bannerManager.get().refreshBanners()
222             advanceTimeBy(100)
223             composeTestRule.setContent {
224                 callPhotopickerMain(
225                     featureManager = featureManager,
226                     selection = selection,
227                     events = events,
228                 )
229             }
230             composeTestRule.waitForIdle()
231             composeTestRule.onNode(hasText(expectedPrivacyMessage)).assertIsNotDisplayed()
232         }
233 }
234