1 /* 2 * Copyright 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 * https://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 */ 18 19 package com.android.healthconnect.controller.tests.navigation 20 21 import android.Manifest 22 import android.content.ComponentName 23 import android.content.Intent 24 import android.content.Intent.EXTRA_PACKAGE_NAME 25 import android.content.Intent.FLAG_ACTIVITY_NEW_TASK 26 import android.content.Intent.makeMainActivity 27 import android.health.connect.HealthConnectManager.ACTION_HEALTH_HOME_SETTINGS 28 import android.health.connect.HealthConnectManager.ACTION_MANAGE_HEALTH_DATA 29 import android.health.connect.HealthConnectManager.ACTION_MANAGE_HEALTH_PERMISSIONS 30 import android.health.connect.HealthDataCategory 31 import android.platform.test.annotations.DisableFlags 32 import android.platform.test.annotations.EnableFlags 33 import android.platform.test.flag.junit.SetFlagsRule 34 import androidx.lifecycle.Lifecycle 35 import androidx.lifecycle.MediatorLiveData 36 import androidx.lifecycle.MutableLiveData 37 import androidx.test.core.app.ActivityScenario.launchActivityForResult 38 import androidx.test.espresso.Espresso.onIdle 39 import androidx.test.espresso.Espresso.onView 40 import androidx.test.espresso.assertion.ViewAssertions.matches 41 import androidx.test.espresso.matcher.ViewMatchers.isDisplayed 42 import androidx.test.espresso.matcher.ViewMatchers.withId 43 import androidx.test.espresso.matcher.ViewMatchers.withText 44 import androidx.test.platform.app.InstrumentationRegistry 45 import com.android.healthconnect.controller.R 46 import com.android.healthconnect.controller.autodelete.AutoDeleteRange.* 47 import com.android.healthconnect.controller.categories.HealthDataCategoryViewModel 48 import com.android.healthconnect.controller.categories.HealthDataCategoryViewModel.* 49 import com.android.healthconnect.controller.categories.HealthDataCategoryViewModel.CategoriesFragmentState.WithData 50 import com.android.healthconnect.controller.data.alldata.AllDataViewModel 51 import com.android.healthconnect.controller.data.appdata.PermissionTypesPerCategory 52 import com.android.healthconnect.controller.exportimport.api.ExportStatusViewModel 53 import com.android.healthconnect.controller.exportimport.api.ScheduledExportUiState 54 import com.android.healthconnect.controller.exportimport.api.ScheduledExportUiStatus 55 import com.android.healthconnect.controller.home.HomeViewModel 56 import com.android.healthconnect.controller.migration.MigrationViewModel 57 import com.android.healthconnect.controller.migration.MigrationViewModel.MigrationFragmentState.* 58 import com.android.healthconnect.controller.migration.api.MigrationRestoreState 59 import com.android.healthconnect.controller.migration.api.MigrationRestoreState.DataRestoreUiError 60 import com.android.healthconnect.controller.migration.api.MigrationRestoreState.DataRestoreUiState 61 import com.android.healthconnect.controller.migration.api.MigrationRestoreState.MigrationUiState 62 import com.android.healthconnect.controller.navigation.TrampolineActivity 63 import com.android.healthconnect.controller.permissions.app.AppPermissionViewModel 64 import com.android.healthconnect.controller.permissions.data.FitnessPermissionType 65 import com.android.healthconnect.controller.permissions.data.HealthPermission.FitnessPermission 66 import com.android.healthconnect.controller.permissions.data.PermissionsAccessType 67 import com.android.healthconnect.controller.selectabledeletion.DeletionDataViewModel 68 import com.android.healthconnect.controller.shared.app.ConnectedAppMetadata 69 import com.android.healthconnect.controller.shared.app.ConnectedAppStatus 70 import com.android.healthconnect.controller.tests.utils.NOW 71 import com.android.healthconnect.controller.tests.utils.TEST_APP 72 import com.android.healthconnect.controller.tests.utils.TEST_APP_NAME 73 import com.android.healthconnect.controller.tests.utils.TEST_APP_PACKAGE_NAME 74 import com.android.healthconnect.controller.tests.utils.di.FakeDeviceInfoUtils 75 import com.android.healthconnect.controller.tests.utils.showOnboarding 76 import com.android.healthconnect.controller.utils.DeviceInfoUtils 77 import com.android.healthconnect.controller.utils.DeviceInfoUtilsModule 78 import com.android.healthfitness.flags.Flags 79 import dagger.hilt.android.testing.BindValue 80 import dagger.hilt.android.testing.HiltAndroidRule 81 import dagger.hilt.android.testing.HiltAndroidTest 82 import dagger.hilt.android.testing.UninstallModules 83 import java.time.Instant 84 import org.junit.Assert.assertEquals 85 import org.junit.Before 86 import org.junit.Rule 87 import org.junit.Test 88 import org.mockito.Mockito 89 import org.mockito.Mockito.* 90 import org.mockito.kotlin.whenever 91 92 @HiltAndroidTest 93 @UninstallModules(DeviceInfoUtilsModule::class) 94 class TrampolineActivityTest { 95 96 @get:Rule val hiltRule = HiltAndroidRule(this) 97 @get:Rule val setFlagsRule = SetFlagsRule() 98 99 @BindValue val deviceInfoUtils: DeviceInfoUtils = FakeDeviceInfoUtils() 100 @BindValue val migrationViewModel: MigrationViewModel = mock(MigrationViewModel::class.java) 101 @BindValue 102 val exportStatusViewModel: ExportStatusViewModel = mock(ExportStatusViewModel::class.java) 103 @BindValue 104 val categoryViewModel: HealthDataCategoryViewModel = 105 mock(HealthDataCategoryViewModel::class.java) 106 @BindValue 107 val appPermissionViewModel: AppPermissionViewModel = mock(AppPermissionViewModel::class.java) 108 @BindValue val allDataViewModel: AllDataViewModel = Mockito.mock(AllDataViewModel::class.java) 109 @BindValue val homeViewModel: HomeViewModel = mock(HomeViewModel::class.java) 110 111 private val context = InstrumentationRegistry.getInstrumentation().context 112 113 @Before setupnull114 fun setup() { 115 hiltRule.inject() 116 117 // Required for aconfig flag reading for tests run on pre V devices 118 InstrumentationRegistry.getInstrumentation() 119 .getUiAutomation() 120 .adoptShellPermissionIdentity(Manifest.permission.READ_DEVICE_CONFIG) 121 122 showOnboarding(context, show = false) 123 (deviceInfoUtils as FakeDeviceInfoUtils).setHealthConnectAvailable(true) 124 125 // Disable migration to show MainActivity and DataManagementActivity 126 whenever(migrationViewModel.getCurrentMigrationUiState()).then { 127 MigrationRestoreState( 128 migrationUiState = MigrationUiState.IDLE, 129 dataRestoreState = DataRestoreUiState.IDLE, 130 dataRestoreError = DataRestoreUiError.ERROR_NONE, 131 ) 132 } 133 whenever(migrationViewModel.migrationState).then { 134 MutableLiveData( 135 WithData( 136 MigrationRestoreState( 137 migrationUiState = MigrationUiState.IDLE, 138 dataRestoreState = DataRestoreUiState.IDLE, 139 dataRestoreError = DataRestoreUiError.ERROR_NONE, 140 ) 141 ) 142 ) 143 } 144 whenever(exportStatusViewModel.storedScheduledExportStatus).then { 145 MutableLiveData( 146 ScheduledExportUiStatus.WithData( 147 ScheduledExportUiState( 148 NOW, 149 ScheduledExportUiState.DataExportError.DATA_EXPORT_ERROR_NONE, 150 1, 151 ) 152 ) 153 ) 154 } 155 val writePermission = 156 FitnessPermission(FitnessPermissionType.EXERCISE, PermissionsAccessType.WRITE) 157 val readPermission = 158 FitnessPermission(FitnessPermissionType.DISTANCE, PermissionsAccessType.READ) 159 whenever(appPermissionViewModel.appInfo).then { MutableLiveData(TEST_APP) } 160 whenever( 161 appPermissionViewModel.shouldNavigateToAppPermissionsFragment(TEST_APP_PACKAGE_NAME) 162 ) 163 .then { true } 164 whenever(appPermissionViewModel.fitnessPermissions).then { 165 MutableLiveData(listOf(writePermission, readPermission)) 166 } 167 whenever(appPermissionViewModel.grantedFitnessPermissions).then { 168 MutableLiveData(setOf(writePermission)) 169 } 170 whenever(appPermissionViewModel.revokeAllHealthPermissionsState).then { 171 MutableLiveData(AppPermissionViewModel.RevokeAllState.NotStarted) 172 } 173 whenever(appPermissionViewModel.allFitnessPermissionsGranted).then { 174 MediatorLiveData(false) 175 } 176 whenever(appPermissionViewModel.atLeastOneFitnessPermissionGranted).then { 177 MediatorLiveData(true) 178 } 179 whenever(appPermissionViewModel.atLeastOneHealthPermissionGranted).then { 180 MediatorLiveData(true) 181 } 182 val accessDate = Instant.parse("2022-10-20T18:40:13.00Z") 183 whenever(appPermissionViewModel.loadAccessDate(anyString())).thenReturn(accessDate) 184 whenever(appPermissionViewModel.lastReadPermissionDisconnected).then { 185 MutableLiveData(false) 186 } 187 whenever(allDataViewModel.allData).then { 188 MutableLiveData<AllDataViewModel.AllDataState>( 189 AllDataViewModel.AllDataState.WithData( 190 listOf( 191 PermissionTypesPerCategory( 192 HealthDataCategory.ACTIVITY, 193 listOf(FitnessPermissionType.STEPS), 194 ) 195 ) 196 ) 197 ) 198 } 199 whenever(allDataViewModel.setOfPermissionTypesToBeDeleted).then { 200 MutableLiveData<Set<FitnessPermissionType>>(emptySet()) 201 } 202 whenever(allDataViewModel.deletionScreenState).then { 203 MutableLiveData(DeletionDataViewModel.DeletionScreenState.VIEW) 204 } 205 whenever(allDataViewModel.getDeletionScreenStateValue()) 206 .thenReturn(DeletionDataViewModel.DeletionScreenState.VIEW) 207 whenever(homeViewModel.connectedApps).then { 208 MutableLiveData(listOf(ConnectedAppMetadata(TEST_APP, ConnectedAppStatus.ALLOWED))) 209 } 210 whenever(homeViewModel.hasAnyMedicalData).then { MutableLiveData(false) } 211 whenever(homeViewModel.showLockScreenBanner).then { 212 MediatorLiveData(HomeViewModel.LockScreenBannerState.NoBanner) 213 } 214 } 215 216 @Test startingActivity_healthConnectNotAvailable_finishesActivitynull217 fun startingActivity_healthConnectNotAvailable_finishesActivity() { 218 (deviceInfoUtils as FakeDeviceInfoUtils).setHealthConnectAvailable(false) 219 220 val scenario = launchActivityForResult<TrampolineActivity>(createStartIntent()) 221 222 onIdle() 223 assertEquals(Lifecycle.State.DESTROYED, scenario.state) 224 } 225 226 @Test startingActivity_noAction_finishesActivitynull227 fun startingActivity_noAction_finishesActivity() { 228 val scenario = launchActivityForResult<TrampolineActivity>(createStartIntent("no_action")) 229 230 onIdle() 231 assertEquals(Lifecycle.State.DESTROYED, scenario.state) 232 } 233 234 @Test homeSettingsAction_onboardingNotDone_redirectsToOnboardingnull235 fun homeSettingsAction_onboardingNotDone_redirectsToOnboarding() { 236 showOnboarding(context, true) 237 238 launchActivityForResult<TrampolineActivity>(createStartIntent()) 239 240 onIdle() 241 onView(withId(R.id.onboarding)).check(matches(isDisplayed())) 242 } 243 244 @Test homeSettingsIntent_launchesMainActivitynull245 fun homeSettingsIntent_launchesMainActivity() { 246 (deviceInfoUtils as FakeDeviceInfoUtils).setHealthConnectAvailable(true) 247 248 launchActivityForResult<TrampolineActivity>(createStartIntent(ACTION_HEALTH_HOME_SETTINGS)) 249 250 onIdle() 251 onView(withText("Recent access")).check(matches(isDisplayed())) 252 onView(withText("Permissions and data")).check(matches(isDisplayed())) 253 } 254 255 @Test 256 @DisableFlags(Flags.FLAG_NEW_INFORMATION_ARCHITECTURE) manageHealthDataIntent_launchesDataManagementActivity_oldIAnull257 fun manageHealthDataIntent_launchesDataManagementActivity_oldIA() { 258 // setup data management screen. 259 whenever(categoryViewModel.categoriesData).then { 260 MutableLiveData<CategoriesFragmentState>(WithData(emptyList())) 261 } 262 263 launchActivityForResult<TrampolineActivity>(createStartIntent(ACTION_MANAGE_HEALTH_DATA)) 264 265 onIdle() 266 onView(withText("Browse data")).check(matches(isDisplayed())) 267 } 268 269 @Test 270 @EnableFlags(Flags.FLAG_NEW_INFORMATION_ARCHITECTURE) manageHealthDataIntent_launchesDataManagementActivity_newIAnull271 fun manageHealthDataIntent_launchesDataManagementActivity_newIA() { 272 // setup data management screen. 273 whenever(categoryViewModel.categoriesData).then { 274 MutableLiveData<CategoriesFragmentState>(WithData(emptyList())) 275 } 276 277 launchActivityForResult<TrampolineActivity>(createStartIntent(ACTION_MANAGE_HEALTH_DATA)) 278 279 onIdle() 280 onView(withText("Activity")).check(matches(isDisplayed())) 281 onView(withText("Steps")).check(matches(isDisplayed())) 282 } 283 284 @Test manageHealthPermissions_launchesSettingsActivitynull285 fun manageHealthPermissions_launchesSettingsActivity() { 286 launchActivityForResult<TrampolineActivity>( 287 createStartIntent(ACTION_MANAGE_HEALTH_PERMISSIONS) 288 ) 289 290 onView( 291 withText( 292 "Apps with this permission can read and write your" + 293 " health and fitness data." 294 ) 295 ) 296 .check(matches(isDisplayed())) 297 } 298 299 @Test manageHealthPermissions_withPackageName_launchesSettingsActivitynull300 fun manageHealthPermissions_withPackageName_launchesSettingsActivity() { 301 val intent = createStartIntent(ACTION_MANAGE_HEALTH_PERMISSIONS) 302 intent.putExtra(EXTRA_PACKAGE_NAME, TEST_APP_PACKAGE_NAME) 303 whenever(appPermissionViewModel.showDisableExerciseRouteEvent) 304 .thenReturn(MediatorLiveData(AppPermissionViewModel.DisableExerciseRouteDialogEvent())) 305 306 launchActivityForResult<TrampolineActivity>(intent) 307 308 onIdle() 309 onView(withText(TEST_APP_NAME)).check(matches(isDisplayed())) 310 } 311 createStartIntentnull312 private fun createStartIntent(action: String = ACTION_HEALTH_HOME_SETTINGS): Intent { 313 return makeMainActivity(ComponentName(context, TrampolineActivity::class.java)) 314 .addFlags(FLAG_ACTIVITY_NEW_TASK) 315 .setAction(action) 316 } 317 } 318