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