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