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