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 package com.android.healthconnect.controller.tests.route
17 
18 import android.app.Activity
19 import android.content.ComponentName
20 import android.content.Context
21 import android.content.Intent
22 import android.health.connect.HealthConnectManager.EXTRA_EXERCISE_ROUTE
23 import android.health.connect.HealthConnectManager.EXTRA_SESSION_ID
24 import android.health.connect.datatypes.ExerciseRoute
25 import android.health.connect.datatypes.ExerciseSessionRecord
26 import android.health.connect.datatypes.ExerciseSessionType
27 import android.widget.Button
28 import android.widget.LinearLayout
29 import androidx.lifecycle.Lifecycle
30 import androidx.lifecycle.MutableLiveData
31 import androidx.test.core.app.ActivityScenario.launchActivityForResult
32 import androidx.test.espresso.Espresso.onView
33 import androidx.test.espresso.action.ViewActions
34 import androidx.test.espresso.action.ViewActions.scrollTo
35 import androidx.test.espresso.assertion.ViewAssertions
36 import androidx.test.espresso.assertion.ViewAssertions.matches
37 import androidx.test.espresso.matcher.RootMatchers.isDialog
38 import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
39 import androidx.test.espresso.matcher.ViewMatchers.withId
40 import androidx.test.espresso.matcher.ViewMatchers.withText
41 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
42 import com.android.healthconnect.controller.R
43 import com.android.healthconnect.controller.migration.MigrationViewModel
44 import com.android.healthconnect.controller.migration.MigrationViewModel.MigrationFragmentState.WithData
45 import com.android.healthconnect.controller.migration.api.MigrationRestoreState
46 import com.android.healthconnect.controller.migration.api.MigrationRestoreState.DataRestoreUiError
47 import com.android.healthconnect.controller.migration.api.MigrationRestoreState.DataRestoreUiState
48 import com.android.healthconnect.controller.migration.api.MigrationRestoreState.MigrationUiState
49 import com.android.healthconnect.controller.route.ExerciseRouteViewModel
50 import com.android.healthconnect.controller.route.ExerciseRouteViewModel.SessionWithAttribution
51 import com.android.healthconnect.controller.route.RouteRequestActivity
52 import com.android.healthconnect.controller.tests.utils.TEST_APP
53 import com.android.healthconnect.controller.tests.utils.TEST_APP_PACKAGE_NAME_2
54 import com.android.healthconnect.controller.tests.utils.getMetaData
55 import com.android.healthconnect.controller.tests.utils.setLocale
56 import com.android.healthconnect.controller.tests.utils.toggleAnimation
57 import com.android.healthconnect.controller.utils.logging.DataRestoreElement
58 import com.android.healthconnect.controller.utils.logging.HealthConnectLogger
59 import com.android.healthconnect.controller.utils.logging.MigrationElement
60 import com.android.healthconnect.controller.utils.logging.RouteRequestElement
61 import com.google.common.truth.Truth.assertThat
62 import dagger.hilt.android.testing.BindValue
63 import dagger.hilt.android.testing.HiltAndroidRule
64 import dagger.hilt.android.testing.HiltAndroidTest
65 import java.time.Instant
66 import java.time.ZoneId
67 import java.util.Locale
68 import java.util.TimeZone
69 import kotlinx.coroutines.ExperimentalCoroutinesApi
70 import kotlinx.coroutines.test.runTest
71 import org.junit.After
72 import org.junit.Assert.assertEquals
73 import org.junit.Before
74 import org.junit.Rule
75 import org.junit.Test
76 import org.mockito.Mockito
77 import org.mockito.kotlin.mock
78 import org.mockito.kotlin.reset
79 import org.mockito.kotlin.verify
80 import org.mockito.kotlin.whenever
81 
82 @ExperimentalCoroutinesApi
83 @HiltAndroidTest
84 class RouteRequestActivityTest {
85 
86     private val START = Instant.ofEpochMilli(1234567891011)
87     private val TEST_SESSION =
88         ExerciseSessionRecord.Builder(
89                 getMetaData(),
90                 START,
91                 START.plusMillis(123456),
92                 ExerciseSessionType.EXERCISE_SESSION_TYPE_RUNNING,
93             )
94             .setTitle("Session title")
95             .setRoute(
96                 ExerciseRoute(
97                     listOf(
98                         ExerciseRoute.Location.Builder(START.plusSeconds(12), 52.26019, 21.02268)
99                             .build(),
100                         ExerciseRoute.Location.Builder(START.plusSeconds(40), 52.26000, 21.02360)
101                             .build(),
102                         ExerciseRoute.Location.Builder(START.plusSeconds(48), 52.25973, 21.02356)
103                             .build(),
104                         ExerciseRoute.Location.Builder(START.plusSeconds(60), 52.25966, 21.02313)
105                             .build(),
106                         ExerciseRoute.Location.Builder(START.plusSeconds(78), 52.25993, 21.02309)
107                             .build(),
108                         ExerciseRoute.Location.Builder(START.plusSeconds(79), 52.25972, 21.02271)
109                             .build(),
110                         ExerciseRoute.Location.Builder(START.plusSeconds(90), 52.25948, 21.02276)
111                             .build(),
112                         ExerciseRoute.Location.Builder(START.plusSeconds(93), 52.25945, 21.02335)
113                             .build(),
114                         ExerciseRoute.Location.Builder(START.plusSeconds(94), 52.25960, 21.02338)
115                             .build(),
116                         ExerciseRoute.Location.Builder(START.plusSeconds(100), 52.25961, 21.02382)
117                             .build(),
118                         ExerciseRoute.Location.Builder(START.plusSeconds(102), 52.25954, 21.02370)
119                             .build(),
120                         ExerciseRoute.Location.Builder(START.plusSeconds(105), 52.25945, 21.02362)
121                             .build(),
122                         ExerciseRoute.Location.Builder(START.plusSeconds(109), 52.25954, 21.02354)
123                             .build(),
124                     )
125                 )
126             )
127             .build()
128 
129     @get:Rule val hiltRule = HiltAndroidRule(this)
130     @BindValue
131     val viewModel: ExerciseRouteViewModel = Mockito.mock(ExerciseRouteViewModel::class.java)
132     @BindValue
133     val migrationViewModel: MigrationViewModel = Mockito.mock(MigrationViewModel::class.java)
134     private lateinit var context: Context
135     @BindValue val healthConnectLogger: HealthConnectLogger = mock()
136 
137     @Before
138     fun setup() {
139         hiltRule.inject()
140         context = getInstrumentation().context
141         context.setLocale(Locale.US)
142         TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("UTC")))
143 
144         whenever(migrationViewModel.migrationState).then {
145             MutableLiveData(
146                 WithData(
147                     MigrationRestoreState(
148                         migrationUiState = MigrationUiState.IDLE,
149                         dataRestoreState = DataRestoreUiState.IDLE,
150                         dataRestoreError = DataRestoreUiError.ERROR_NONE,
151                     )
152                 )
153             )
154         }
155 
156         whenever(viewModel.isReadRoutesPermissionDeclared(context.packageName)).thenReturn(true)
157         whenever(viewModel.isSessionInaccessible(context.packageName, TEST_SESSION))
158             .thenReturn(false)
159         // disable animations
160         toggleAnimation(false)
161     }
162 
163     @After
164     fun tearDown() {
165         reset(healthConnectLogger)
166         // enable animations
167         toggleAnimation(true)
168     }
169 
170     @Test
171     fun intentLaunchesRouteRequestActivity_noSessionId() {
172         val startActivityIntent =
173             Intent.makeMainActivity(ComponentName(context, RouteRequestActivity::class.java))
174                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
175                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
176 
177         whenever(viewModel.exerciseSession).then {
178             MutableLiveData(SessionWithAttribution(TEST_SESSION, TEST_APP))
179         }
180 
181         val scenario = launchActivityForResult<RouteRequestActivity>(startActivityIntent)
182 
183         assertThat(scenario.getResult().getResultCode()).isEqualTo(Activity.RESULT_CANCELED)
184         val returnedIntent = scenario.getResult().getResultData()
185         assertThat(returnedIntent.hasExtra(EXTRA_EXERCISE_ROUTE)).isFalse()
186     }
187 
188     @Test
189     fun intentLaunchesRouteRequestActivity_nullSessionId() {
190         val startActivityIntent =
191             Intent.makeMainActivity(ComponentName(context, RouteRequestActivity::class.java))
192                 .putExtra(EXTRA_SESSION_ID, null as String?)
193                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
194                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
195 
196         whenever(viewModel.exerciseSession).then {
197             MutableLiveData(SessionWithAttribution(TEST_SESSION, TEST_APP))
198         }
199 
200         val scenario = launchActivityForResult<RouteRequestActivity>(startActivityIntent)
201 
202         assertThat(scenario.getResult().getResultCode()).isEqualTo(Activity.RESULT_CANCELED)
203         val returnedIntent = scenario.getResult().getResultData()
204         assertThat(returnedIntent.hasExtra(EXTRA_EXERCISE_ROUTE)).isFalse()
205     }
206 
207     @Test
208     fun intentLaunchesRouteRequestActivity_noRoute() {
209         val startActivityIntent = getRouteActivityIntent()
210 
211         whenever(viewModel.exerciseSession).then {
212             MutableLiveData(
213                 SessionWithAttribution(
214                     ExerciseSessionRecord.Builder(
215                             getMetaData(),
216                             START,
217                             START.plusMillis(123456),
218                             ExerciseSessionType.EXERCISE_SESSION_TYPE_RUNNING,
219                         )
220                         .setTitle("Session title")
221                         .setRoute(ExerciseRoute(listOf()))
222                         .build(),
223                     TEST_APP,
224                 )
225             )
226         }
227 
228         val scenario = launchActivityForResult<RouteRequestActivity>(startActivityIntent)
229 
230         assertThat(scenario.getResult().getResultCode()).isEqualTo(Activity.RESULT_CANCELED)
231         val returnedIntent = scenario.getResult().getResultData()
232         assertThat(returnedIntent.hasExtra(EXTRA_EXERCISE_ROUTE)).isFalse()
233     }
234 
235     @Test
236     fun intentLaunchesRouteRequestActivity_emptyRoute() {
237         val startActivityIntent = getRouteActivityIntent()
238 
239         whenever(viewModel.exerciseSession).then {
240             MutableLiveData(
241                 SessionWithAttribution(
242                     ExerciseSessionRecord.Builder(
243                             getMetaData(),
244                             START,
245                             START.plusMillis(123456),
246                             ExerciseSessionType.EXERCISE_SESSION_TYPE_RUNNING,
247                         )
248                         .setTitle("Session title")
249                         .setRoute(ExerciseRoute(listOf()))
250                         .build(),
251                     TEST_APP,
252                 )
253             )
254         }
255 
256         val scenario = launchActivityForResult<RouteRequestActivity>(startActivityIntent)
257 
258         assertThat(scenario.getResult().getResultCode()).isEqualTo(Activity.RESULT_CANCELED)
259         val returnedIntent = scenario.getResult().getResultData()
260         assertThat(returnedIntent.hasExtra(EXTRA_EXERCISE_ROUTE)).isFalse()
261     }
262 
263     @Test
264     fun intentLaunchesRouteRequestActivity() {
265         val startActivityIntent =
266             Intent.makeMainActivity(ComponentName(context, RouteRequestActivity::class.java))
267                 .putExtra(EXTRA_SESSION_ID, "sessionID")
268                 .putExtra(Intent.EXTRA_PACKAGE_NAME, TEST_APP_PACKAGE_NAME_2)
269                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
270                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
271 
272         whenever(viewModel.exerciseSession).then {
273             MutableLiveData(SessionWithAttribution(TEST_SESSION, TEST_APP))
274         }
275 
276         launchActivityForResult<RouteRequestActivity>(startActivityIntent)
277 
278         onView(withText("This app will be able to read your past location in the route"))
279             .inRoot(isDialog())
280             .check(matches(isDisplayed()))
281         onView(withText("Allow this route")).inRoot(isDialog()).check(matches(isDisplayed()))
282         onView(withText("Allow Health Connect to access this exercise route in Health Connect?"))
283             .inRoot(isDialog())
284             .check(matches(isDisplayed()))
285         onView(withText("Session title")).inRoot(isDialog()).check(matches(isDisplayed()))
286         onView(withText("February 13, 2009 • Health Connect test app"))
287             .inRoot(isDialog())
288             .check(matches(isDisplayed()))
289         onView(withId(R.id.map_view)).inRoot(isDialog()).check(matches(isDisplayed()))
290         onView(withText("Don\'t allow"))
291             .inRoot(isDialog())
292             .perform(scrollTo())
293             .check(matches(isDisplayed()))
294         verify(healthConnectLogger)
295             .logImpression(RouteRequestElement.EXERCISE_ROUTE_REQUEST_DIALOG_CONTAINER)
296         verify(healthConnectLogger)
297             .logImpression(RouteRequestElement.EXERCISE_ROUTE_DIALOG_ROUTE_VIEW)
298         verify(healthConnectLogger)
299             .logImpression(RouteRequestElement.EXERCISE_ROUTE_DIALOG_INFORMATION_BUTTON)
300         verify(healthConnectLogger)
301             .logImpression(RouteRequestElement.EXERCISE_ROUTE_DIALOG_ALLOW_BUTTON)
302         verify(healthConnectLogger)
303             .logImpression(RouteRequestElement.EXERCISE_ROUTE_DIALOG_DONT_ALLOW_BUTTON)
304     }
305 
306     @Test
307     fun intentLaunchesRouteRequestActivity_sessionWithNoTitle() {
308         val startActivityIntent =
309             Intent.makeMainActivity(ComponentName(context, RouteRequestActivity::class.java))
310                 .putExtra(EXTRA_SESSION_ID, "sessionID")
311                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
312                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
313 
314         whenever(viewModel.exerciseSession).then {
315             MutableLiveData(
316                 SessionWithAttribution(
317                     ExerciseSessionRecord.Builder(
318                             getMetaData(),
319                             START,
320                             START.plusMillis(123456),
321                             ExerciseSessionType.EXERCISE_SESSION_TYPE_RUNNING,
322                         )
323                         .setRoute(
324                             ExerciseRoute(
325                                 listOf(
326                                     ExerciseRoute.Location.Builder(
327                                             START.plusSeconds(12),
328                                             52.26019,
329                                             21.02268,
330                                         )
331                                         .build(),
332                                     ExerciseRoute.Location.Builder(
333                                             START.plusSeconds(40),
334                                             52.26000,
335                                             21.02360,
336                                         )
337                                         .build(),
338                                 )
339                             )
340                         )
341                         .build(),
342                     TEST_APP,
343                 )
344             )
345         }
346 
347         launchActivityForResult<RouteRequestActivity>(startActivityIntent)
348 
349         onView(withText("Allow this route")).inRoot(isDialog()).check(matches(isDisplayed()))
350         onView(withText("Allow Health Connect to access this exercise route in Health Connect?"))
351             .inRoot(isDialog())
352             .check(matches(isDisplayed()))
353         onView(withText("This app will be able to read your past location in the route"))
354             .inRoot(isDialog())
355             .check(matches(isDisplayed()))
356         onView(withText("Running")).inRoot(isDialog()).check(matches(isDisplayed()))
357         onView(withText("February 13, 2009 • Health Connect test app"))
358             .inRoot(isDialog())
359             .check(matches(isDisplayed()))
360         onView(withId(R.id.map_view)).inRoot(isDialog()).check(matches(isDisplayed()))
361         onView(withText("Don\'t allow"))
362             .inRoot(isDialog())
363             .perform(scrollTo())
364             .check(matches(isDisplayed()))
365     }
366 
367     @Test
368     fun intentLaunchesRouteRequestActivity_infoDialog() {
369         val startActivityIntent =
370             Intent.makeMainActivity(ComponentName(context, RouteRequestActivity::class.java))
371                 .putExtra(EXTRA_SESSION_ID, "sessionID")
372                 .putExtra(Intent.EXTRA_PACKAGE_NAME, TEST_APP_PACKAGE_NAME_2)
373                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
374                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
375 
376         whenever(viewModel.exerciseSession).then {
377             MutableLiveData(SessionWithAttribution(TEST_SESSION, TEST_APP))
378         }
379 
380         val scenario = launchActivityForResult<RouteRequestActivity>(startActivityIntent)
381 
382         scenario.onActivity { activity: RouteRequestActivity ->
383             activity.dialog?.findViewById<LinearLayout>(R.id.more_info)?.callOnClick()
384         }
385 
386         verify(healthConnectLogger)
387             .logInteraction(RouteRequestElement.EXERCISE_ROUTE_DIALOG_INFORMATION_BUTTON)
388 
389         onView(withText("Exercise routes include location information"))
390             .inRoot(isDialog())
391             .check(matches(isDisplayed()))
392         onView(withText("Who can see this data?")).inRoot(isDialog()).check(matches(isDisplayed()))
393         onView(withText("Only apps you allow to access your exercise routes"))
394             .inRoot(isDialog())
395             .check(matches(isDisplayed()))
396         onView(withText("How can I manage access?"))
397             .inRoot(isDialog())
398             .check(matches(isDisplayed()))
399         onView(withText("You can manage app access to exercise routes in Health Connect settings"))
400             .inRoot(isDialog())
401             .check(matches(isDisplayed()))
402         verify(healthConnectLogger)
403             .logImpression(RouteRequestElement.EXERCISE_ROUTE_EDUCATION_DIALOG_CONTAINER)
404         verify(healthConnectLogger)
405             .logImpression(RouteRequestElement.EXERCISE_ROUTE_EDUCATION_DIALOG_BACK_BUTTON)
406     }
407 
408     @Test
409     fun intentLaunchesRouteRequestActivity_dontAllow() {
410         val startActivityIntent = getRouteActivityIntent()
411 
412         whenever(viewModel.exerciseSession).then {
413             MutableLiveData(SessionWithAttribution(TEST_SESSION, TEST_APP))
414         }
415 
416         val scenario = launchActivityForResult<RouteRequestActivity>(startActivityIntent)
417 
418         scenario.onActivity { activity: RouteRequestActivity ->
419             activity.dialog?.findViewById<Button>(R.id.route_dont_allow_button)?.callOnClick()
420         }
421 
422         verify(healthConnectLogger)
423             .logInteraction(RouteRequestElement.EXERCISE_ROUTE_DIALOG_DONT_ALLOW_BUTTON)
424         assertThat(scenario.getResult().getResultCode()).isEqualTo(Activity.RESULT_CANCELED)
425         val returnedIntent = scenario.getResult().getResultData()
426         assertThat(returnedIntent.hasExtra(EXTRA_EXERCISE_ROUTE)).isFalse()
427     }
428 
429     @Test
430     fun intentLaunchesRouteRequestActivity_allow() {
431         val startActivityIntent = getRouteActivityIntent()
432 
433         whenever(viewModel.exerciseSession).then {
434             MutableLiveData(SessionWithAttribution(TEST_SESSION, TEST_APP))
435         }
436 
437         val scenario = launchActivityForResult<RouteRequestActivity>(startActivityIntent)
438 
439         scenario.onActivity { activity: RouteRequestActivity ->
440             activity.dialog?.findViewById<Button>(R.id.route_allow_button)?.callOnClick()
441         }
442 
443         verify(healthConnectLogger)
444             .logInteraction(RouteRequestElement.EXERCISE_ROUTE_DIALOG_ALLOW_BUTTON)
445 
446         assertThat(scenario.getResult().getResultCode()).isEqualTo(Activity.RESULT_OK)
447         val returnedIntent = scenario.getResult().getResultData()
448         assertThat(returnedIntent.hasExtra(EXTRA_EXERCISE_ROUTE)).isTrue()
449         assertThat(returnedIntent.getParcelableExtra<ExerciseRoute>(EXTRA_EXERCISE_ROUTE))
450             .isEqualTo(TEST_SESSION.route)
451     }
452 
453     @Test
454     fun intentLaunchesRouteRequestActivity_alwaysAllow() {
455         val startActivityIntent = getRouteActivityIntent()
456         whenever(viewModel.exerciseSession).then {
457             MutableLiveData(SessionWithAttribution(TEST_SESSION, TEST_APP))
458         }
459 
460         val scenario = launchActivityForResult<RouteRequestActivity>(startActivityIntent)
461 
462         scenario.onActivity { activity: RouteRequestActivity ->
463             activity.dialog?.findViewById<Button>(R.id.route_allow_all_button)?.callOnClick()
464         }
465 
466         verify(healthConnectLogger)
467             .logInteraction(RouteRequestElement.EXERCISE_ROUTE_DIALOG_ALWAYS_ALLOW_BUTTON)
468         assertThat(scenario.getResult().getResultCode()).isEqualTo(Activity.RESULT_OK)
469         val returnedIntent = scenario.getResult().getResultData()
470         assertThat(returnedIntent.hasExtra(EXTRA_EXERCISE_ROUTE)).isTrue()
471         assertThat(returnedIntent.getParcelableExtra<ExerciseRoute>(EXTRA_EXERCISE_ROUTE))
472             .isEqualTo(TEST_SESSION.route)
473         verify(viewModel).grantReadRoutesPermission(context.packageName)
474     }
475 
476     @Test
477     fun intent_migrationInProgress_shoesMigrationInProgressDialog() = runTest {
478         whenever(migrationViewModel.getCurrentMigrationUiState()).then {
479             MigrationRestoreState(
480                 migrationUiState = MigrationUiState.IN_PROGRESS,
481                 dataRestoreState = DataRestoreUiState.IDLE,
482                 dataRestoreError = DataRestoreUiError.ERROR_NONE,
483             )
484         }
485         whenever(migrationViewModel.migrationState).then {
486             MutableLiveData(
487                 WithData(
488                     MigrationRestoreState(
489                         migrationUiState = MigrationUiState.IN_PROGRESS,
490                         dataRestoreState = DataRestoreUiState.IDLE,
491                         dataRestoreError = DataRestoreUiError.ERROR_NONE,
492                     )
493                 )
494             )
495         }
496         val startActivityIntent = getRouteActivityIntent()
497 
498         whenever(viewModel.exerciseSession).then {
499             MutableLiveData(SessionWithAttribution(TEST_SESSION, TEST_APP))
500         }
501 
502         val scenario = launchActivityForResult<RouteRequestActivity>(startActivityIntent)
503         onView(
504                 withText(
505                     "Health Connect is being integrated with the Android system.\n\nYou'll get a notification when the process is complete and you can use Health Connect with Health Connect."
506                 )
507             )
508             .inRoot(isDialog())
509             .check(matches(isDisplayed()))
510         onView(withText("Got it")).inRoot(isDialog()).check(matches(isDisplayed()))
511 
512         verify(healthConnectLogger)
513             .logImpression(MigrationElement.MIGRATION_IN_PROGRESS_DIALOG_CONTAINER)
514         verify(healthConnectLogger)
515             .logImpression(MigrationElement.MIGRATION_IN_PROGRESS_DIALOG_BUTTON)
516 
517         onView(withText("Got it")).inRoot(isDialog()).perform(ViewActions.click())
518         verify(healthConnectLogger)
519             .logInteraction(MigrationElement.MIGRATION_IN_PROGRESS_DIALOG_BUTTON)
520 
521         // Needed to make sure activity is destroyed
522         Thread.sleep(2_000)
523         assertEquals(Lifecycle.State.DESTROYED, scenario.state)
524     }
525 
526     @Test
527     fun intent_restoreInProgress_showsRestoreInProgressDialog() = runTest {
528         whenever(migrationViewModel.getCurrentMigrationUiState()).then {
529             MigrationRestoreState(
530                 migrationUiState = MigrationUiState.IDLE,
531                 dataRestoreState = DataRestoreUiState.IN_PROGRESS,
532                 dataRestoreError = DataRestoreUiError.ERROR_NONE,
533             )
534         }
535         whenever(migrationViewModel.migrationState).then {
536             MutableLiveData(
537                 WithData(
538                     MigrationRestoreState(
539                         migrationUiState = MigrationUiState.IDLE,
540                         dataRestoreState = DataRestoreUiState.IN_PROGRESS,
541                         dataRestoreError = DataRestoreUiError.ERROR_NONE,
542                     )
543                 )
544             )
545         }
546         val startActivityIntent = getRouteActivityIntent()
547 
548         whenever(viewModel.exerciseSession).then {
549             MutableLiveData(SessionWithAttribution(TEST_SESSION, TEST_APP))
550         }
551 
552         val scenario = launchActivityForResult<RouteRequestActivity>(startActivityIntent)
553 
554         onView(withText("Health Connect restore in progress"))
555             .inRoot(isDialog())
556             .check(matches(isDisplayed()))
557         onView(
558                 withText(
559                     "Health Connect is restoring data and permissions. This may take some time to complete."
560                 )
561             )
562             .inRoot(isDialog())
563             .check(matches(isDisplayed()))
564         onView(withText("Got it")).inRoot(isDialog()).check(matches(isDisplayed()))
565         verify(healthConnectLogger)
566             .logImpression(DataRestoreElement.RESTORE_IN_PROGRESS_DIALOG_CONTAINER)
567         verify(healthConnectLogger)
568             .logImpression(DataRestoreElement.RESTORE_IN_PROGRESS_DIALOG_BUTTON)
569 
570         onView(withText("Got it")).inRoot(isDialog()).perform(ViewActions.click())
571         verify(healthConnectLogger)
572             .logInteraction(DataRestoreElement.RESTORE_IN_PROGRESS_DIALOG_BUTTON)
573 
574         // Needed to make sure activity is destroyed
575         Thread.sleep(2_000)
576         assertEquals(Lifecycle.State.DESTROYED, scenario.state)
577     }
578 
579     @Test
580     fun intent_migrationPending_showsMigrationPendingDialog() = runTest {
581         whenever(migrationViewModel.getCurrentMigrationUiState()).then {
582             MigrationRestoreState(
583                 migrationUiState = MigrationUiState.APP_UPGRADE_REQUIRED,
584                 dataRestoreState = DataRestoreUiState.IDLE,
585                 dataRestoreError = DataRestoreUiError.ERROR_NONE,
586             )
587         }
588         whenever(migrationViewModel.migrationState).then {
589             MutableLiveData(
590                 WithData(
591                     MigrationRestoreState(
592                         migrationUiState = MigrationUiState.APP_UPGRADE_REQUIRED,
593                         dataRestoreState = DataRestoreUiState.IDLE,
594                         dataRestoreError = DataRestoreUiError.ERROR_NONE,
595                     )
596                 )
597             )
598         }
599         val startActivityIntent = getRouteActivityIntent()
600 
601         whenever(viewModel.exerciseSession).then {
602             MutableLiveData(SessionWithAttribution(TEST_SESSION, TEST_APP))
603         }
604 
605         launchActivityForResult<RouteRequestActivity>(startActivityIntent)
606 
607         onView(
608                 withText(
609                     "Health Connect is ready to be integrated with your Android system. If you give Health Connect access now, some features may not work until integration is complete."
610                 )
611             )
612             .inRoot(isDialog())
613             .check(matches(isDisplayed()))
614         // TODO (b/322495982) check navigation to Migration activity
615         onView(withText("Start integration")).inRoot(isDialog()).check(matches(isDisplayed()))
616         onView(withText("Continue")).inRoot(isDialog()).check(matches(isDisplayed()))
617         verify(healthConnectLogger)
618             .logImpression(MigrationElement.MIGRATION_PENDING_DIALOG_CONTAINER)
619         verify(healthConnectLogger)
620             .logImpression(MigrationElement.MIGRATION_PENDING_DIALOG_CONTINUE_BUTTON)
621         verify(healthConnectLogger)
622             .logImpression(MigrationElement.MIGRATION_PENDING_DIALOG_CANCEL_BUTTON)
623 
624         onView(withText("Continue")).inRoot(isDialog()).perform(ViewActions.click())
625         onView(withText("Continue")).check(ViewAssertions.doesNotExist())
626         verify(healthConnectLogger)
627             .logInteraction(MigrationElement.MIGRATION_PENDING_DIALOG_CONTINUE_BUTTON)
628     }
629 
630     private fun getRouteActivityIntent(): Intent {
631         val startActivityIntent =
632             Intent.makeMainActivity(ComponentName(context, RouteRequestActivity::class.java))
633                 .putExtra(Intent.EXTRA_PACKAGE_NAME, TEST_APP_PACKAGE_NAME_2)
634                 .putExtra(EXTRA_SESSION_ID, "sessionID")
635                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
636                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
637         return startActivityIntent
638     }
639 }
640