1 /* 2 * Copyright (C) 2017 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 android.autofillservice.cts; 18 19 import static android.autofillservice.cts.activities.OutOfProcessLoginActivity.getDestroyedMarker; 20 import static android.autofillservice.cts.activities.OutOfProcessLoginActivity.getStartedMarker; 21 import static android.autofillservice.cts.activities.OutOfProcessLoginActivity.getStoppedMarker; 22 import static android.autofillservice.cts.testcore.Helper.ID_LOGIN; 23 import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD; 24 import static android.autofillservice.cts.testcore.Helper.ID_USERNAME; 25 import static android.autofillservice.cts.testcore.Helper.assertTextAndValue; 26 import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId; 27 import static android.autofillservice.cts.testcore.Helper.getContext; 28 import static android.autofillservice.cts.testcore.UiBot.LANDSCAPE; 29 import static android.autofillservice.cts.testcore.UiBot.PORTRAIT; 30 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD; 31 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME; 32 33 import static com.android.compatibility.common.util.ShellUtils.runShellCommand; 34 35 import static com.google.common.truth.Truth.assertThat; 36 import static com.google.common.truth.Truth.assertWithMessage; 37 38 import static org.junit.Assume.assumeFalse; 39 import static org.junit.Assume.assumeTrue; 40 41 import android.app.ActivityManager; 42 import android.app.PendingIntent; 43 import android.app.assist.AssistStructure; 44 import android.autofillservice.cts.activities.EmptyActivity; 45 import android.autofillservice.cts.activities.LoginActivity; 46 import android.autofillservice.cts.activities.ManualAuthenticationActivity; 47 import android.autofillservice.cts.activities.OutOfProcessLoginActivity; 48 import android.autofillservice.cts.commontests.AutoFillServiceTestCase; 49 import android.autofillservice.cts.testcore.CannedFillResponse; 50 import android.autofillservice.cts.testcore.Helper; 51 import android.autofillservice.cts.testcore.InstrumentedAutoFillService; 52 import android.autofillservice.cts.testcore.Timeouts; 53 import android.autofillservice.cts.testcore.UiBot; 54 import android.content.Context; 55 import android.content.Intent; 56 import android.content.IntentSender; 57 import android.os.Bundle; 58 import android.os.SystemClock; 59 import android.platform.test.annotations.AppModeFull; 60 import android.support.test.uiautomator.UiObject2; 61 import android.util.Log; 62 import android.view.autofill.AutofillValue; 63 64 import com.android.compatibility.common.util.Timeout; 65 66 import org.junit.After; 67 import org.junit.Before; 68 import org.junit.Test; 69 70 import java.util.concurrent.Callable; 71 72 /** 73 * Test the lifecycle of a autofill session 74 */ 75 @AppModeFull(reason = "This test requires android.permission.WRITE_EXTERNAL_STORAGE") 76 public class SessionLifecycleTest extends AutoFillServiceTestCase.ManualActivityLaunch { 77 private static final String TAG = "SessionLifecycleTest"; 78 79 private static final String ID_BUTTON = "button"; 80 private static final String ID_CANCEL = "cancel"; 81 82 /** 83 * Delay for activity start/stop. 84 */ 85 // TODO: figure out a better way to wait without using sleep(). 86 private static final long WAIT_ACTIVITY_MS = 1000; 87 88 private static final Timeout SESSION_LIFECYCLE_TIMEOUT = new Timeout( 89 "SESSION_LIFECYCLE_TIMEOUT", 5000, 2F, 5000); 90 91 /** 92 * Runs an {@code assertion}, retrying until {@code timeout} is reached. 93 */ eventually(String description, Callable<Boolean> assertion)94 private static void eventually(String description, Callable<Boolean> assertion) 95 throws Exception { 96 SESSION_LIFECYCLE_TIMEOUT.run(description, assertion); 97 } 98 SessionLifecycleTest()99 public SessionLifecycleTest() { 100 super(new UiBot(SESSION_LIFECYCLE_TIMEOUT)); 101 } 102 103 /** 104 * Prevents the screen to rotate by itself 105 */ 106 @Before disableAutoRotation()107 public void disableAutoRotation() throws Exception { 108 Helper.disableAutoRotation(mUiBot); 109 } 110 111 /** 112 * Allows the screen to rotate by itself 113 */ 114 @After allowAutoRotation()115 public void allowAutoRotation() { 116 Helper.allowAutoRotation(); 117 } 118 119 @After finishLoginActivityOnAnotherProcess()120 public void finishLoginActivityOnAnotherProcess() throws Exception { 121 runShellCommand( 122 "am broadcast --receiver-foreground -n android.autofillservice.cts/.testcore" 123 + ".OutOfProcessLoginActivityFinisherReceiver"); 124 mUiBot.assertGoneByRelativeId(ID_USERNAME, Timeouts.ACTIVITY_RESURRECTION); 125 126 if (!OutOfProcessLoginActivity.hasInstance()) { 127 Log.v(TAG, "@After: Not waiting for oop activity to be destroyed"); 128 return; 129 } 130 // Waiting for activity to be destroyed (destroy marker appears) 131 eventually("getDestroyedMarker()", () -> { 132 return getDestroyedMarker(getContext()).exists(); 133 }); 134 } 135 killOfProcessLoginActivityProcess()136 private void killOfProcessLoginActivityProcess() throws Exception { 137 // Waiting for activity to stop (stop marker appears) 138 eventually("getStoppedMarker()", () -> { 139 return getStoppedMarker(getContext()).exists(); 140 }); 141 142 // onStop might not be finished, hence wait more 143 SystemClock.sleep(WAIT_ACTIVITY_MS); 144 145 // Kill activity that is in the background 146 runShellCommand("am broadcast --receiver-foreground " 147 + "-n android.autofillservice.cts/.testcore.SelfDestructReceiver"); 148 } 149 startAndWaitExternalActivity()150 private void startAndWaitExternalActivity() throws Exception { 151 final Intent outOfProcessAcvitityStartIntent = new Intent(getContext(), 152 OutOfProcessLoginActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 153 getStartedMarker(getContext()).delete(); 154 getContext().startActivity(outOfProcessAcvitityStartIntent); 155 eventually("getStartedMarker()", () -> { 156 return getStartedMarker(getContext()).exists(); 157 }); 158 getStartedMarker(getContext()).delete(); 159 // Even if we wait the activity started, UiObject still fails. Have to wait a little bit. 160 SystemClock.sleep(WAIT_ACTIVITY_MS); 161 162 mUiBot.assertShownByRelativeId(ID_USERNAME); 163 } 164 165 @Test testDatasetAuthResponseWhileAutofilledAppIsLifecycled()166 public void testDatasetAuthResponseWhileAutofilledAppIsLifecycled() throws Exception { 167 assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext)); 168 final ActivityManager activityManager = (ActivityManager) getContext() 169 .getSystemService(Context.ACTIVITY_SERVICE); 170 assumeFalse(activityManager.isLowRamDevice()); 171 172 // Set service. 173 enableService(); 174 175 try { 176 177 // Start activity that is autofilled in a separate process so it can be killed 178 startAndWaitExternalActivity(); 179 180 // Set expectations. 181 final Bundle extras = new Bundle(); 182 extras.putString("numbers", "4815162342"); 183 184 // Create the authentication intent (launching a full screen activity) 185 IntentSender authentication = PendingIntent.getActivity(getContext(), 0, 186 new Intent(getContext(), ManualAuthenticationActivity.class), 187 PendingIntent.FLAG_MUTABLE).getIntentSender(); 188 189 // Prepare the authenticated response 190 ManualAuthenticationActivity.setResponse(new CannedFillResponse.Builder() 191 .addDataset(new CannedFillResponse.CannedDataset.Builder() 192 .setField(ID_USERNAME, AutofillValue.forText("autofilled username")) 193 .setPresentation(createPresentation("dataset")).build()) 194 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD) 195 .setExtras(extras).build()); 196 197 CannedFillResponse response = new CannedFillResponse.Builder() 198 .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD) 199 .setPresentation(createPresentation("authenticate")) 200 .build(); 201 sReplier.addResponse(response); 202 203 // Trigger autofill on username 204 mUiBot.selectByRelativeId(ID_USERNAME); 205 206 // Wait for fill request to be processed 207 sReplier.getNextFillRequest(); 208 209 // Wait until authentication is shown 210 mUiBot.assertDatasets("authenticate"); 211 212 // Change orientation which triggers a destroy -> create in the app as the activity 213 // cannot deal with such situations 214 mUiBot.setScreenOrientation(LANDSCAPE); 215 mUiBot.setScreenOrientation(PORTRAIT); 216 217 // Wait context and Views being recreated in rotation 218 mUiBot.assertShownByRelativeId(ID_USERNAME); 219 220 // Delete stopped marker 221 getStoppedMarker(getContext()).delete(); 222 223 // Authenticate 224 mUiBot.selectDataset("authenticate"); 225 226 // Kill activity that is in the background 227 killOfProcessLoginActivityProcess(); 228 229 // Change orientation which triggers a destroy -> create in the app as the activity 230 // cannot deal with such situations 231 mUiBot.setScreenOrientation(PORTRAIT); 232 233 // Approve authentication 234 mUiBot.selectByRelativeId(ID_BUTTON); 235 236 // Wait for dataset to be shown 237 mUiBot.assertDatasets("dataset"); 238 239 // Change orientation which triggers a destroy -> create in the app as the activity 240 // cannot deal with such situations 241 mUiBot.setScreenOrientation(LANDSCAPE); 242 243 // Select dataset 244 mUiBot.selectDataset("dataset"); 245 246 // Check the results. 247 eventually("getTextById(" + ID_USERNAME + ")", () -> { 248 return mUiBot.getTextByRelativeId(ID_USERNAME).equals("autofilled username"); 249 }); 250 251 // Set password 252 mUiBot.setTextByRelativeId(ID_PASSWORD, "new password"); 253 254 // Login 255 mUiBot.selectByRelativeId(ID_LOGIN); 256 257 // Wait for save UI to be shown 258 mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD); 259 260 // Change orientation to make sure save UI can handle this 261 mUiBot.setScreenOrientation(PORTRAIT); 262 263 // Tap "Save". 264 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD); 265 266 // Get save request 267 InstrumentedAutoFillService.SaveRequest saveRequest = sReplier.getNextSaveRequest(); 268 assertWithMessage("onSave() not called").that(saveRequest).isNotNull(); 269 270 // Make sure data is correctly saved 271 final AssistStructure.ViewNode username = findNodeByResourceId(saveRequest.structure, 272 ID_USERNAME); 273 assertTextAndValue(username, "autofilled username"); 274 final AssistStructure.ViewNode password = findNodeByResourceId(saveRequest.structure, 275 ID_PASSWORD); 276 assertTextAndValue(password, "new password"); 277 278 // Make sure extras were passed back on onSave() 279 assertThat(saveRequest.data).isNotNull(); 280 final String extraValue = saveRequest.data.getString("numbers"); 281 assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342"); 282 } finally { 283 mUiBot.resetScreenResolution(); 284 } 285 } 286 287 @Test testAuthCanceledWhileAutofilledAppIsLifecycled()288 public void testAuthCanceledWhileAutofilledAppIsLifecycled() throws Exception { 289 // Set service. 290 enableService(); 291 292 // Start activity that is autofilled in a separate process so it can be killed 293 startAndWaitExternalActivity(); 294 295 // Create the authentication intent (launching a full screen activity) 296 IntentSender authentication = PendingIntent.getActivity(getContext(), 0, 297 new Intent(getContext(), ManualAuthenticationActivity.class), 298 PendingIntent.FLAG_IMMUTABLE).getIntentSender(); 299 300 CannedFillResponse response = new CannedFillResponse.Builder() 301 .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD) 302 .setPresentation(createPresentation("authenticate")) 303 .build(); 304 sReplier.addResponse(response); 305 306 // Trigger autofill on username 307 mUiBot.selectByRelativeId(ID_USERNAME); 308 309 // Wait for fill request to be processed 310 sReplier.getNextFillRequest(); 311 312 // Wait until authentication is shown 313 mUiBot.assertDatasets("authenticate"); 314 315 // Delete stopped marker 316 getStoppedMarker(getContext()).delete(); 317 318 // Authenticate 319 mUiBot.selectDataset("authenticate"); 320 321 // Kill activity that is in the background 322 killOfProcessLoginActivityProcess(); 323 324 // Set expectations. 325 sReplier.addResponse( 326 new CannedFillResponse.Builder() 327 .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD) 328 .setPresentation(createPresentation("authenticate2")) 329 .build()); 330 331 // Cancel authentication activity 332 mUiBot.pressBack(); 333 334 // Wait for fill request to be processed 335 mUiBot.waitForIdle(); 336 sReplier.getNextFillRequest(); 337 338 // Authentication should still be shown 339 mUiBot.assertDatasets("authenticate2"); 340 } 341 342 @Test testDatasetVisibleWhileAutofilledAppIsLifecycled()343 public void testDatasetVisibleWhileAutofilledAppIsLifecycled() throws Exception { 344 // Set service. 345 enableService(); 346 347 // Start activity that is autofilled in a separate process so it can be killed 348 startAndWaitExternalActivity(); 349 350 CannedFillResponse response = new CannedFillResponse.Builder() 351 .addDataset(new CannedFillResponse.CannedDataset.Builder( 352 createPresentation("dataset")) 353 .setField(ID_USERNAME, "filled").build()) 354 .build(); 355 sReplier.addResponse(response); 356 357 // Trigger autofill on username 358 mUiBot.selectByRelativeId(ID_USERNAME); 359 360 // Wait for fill request to be processed 361 sReplier.getNextFillRequest(); 362 363 // Wait until dataset is shown 364 mUiBot.assertDatasets("dataset"); 365 366 // Delete stopped marker 367 getStoppedMarker(getContext()).delete(); 368 369 // Start an activity on top of the autofilled activity 370 Intent intent = new Intent(getContext(), EmptyActivity.class); 371 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 372 373 getContext().startActivity(intent); 374 375 // Kill activity that is in the background 376 killOfProcessLoginActivityProcess(); 377 378 // Add response for back to the first activity 379 sReplier.addResponse( 380 new CannedFillResponse.Builder() 381 .addDataset(new CannedFillResponse.CannedDataset.Builder( 382 createPresentation("dataset2")) 383 .setField(ID_USERNAME, "filled").build()) 384 .build()); 385 386 // Cancel activity on top 387 mUiBot.pressBack(); 388 389 // Wait for fill request to be processed 390 mUiBot.waitForIdle(); 391 sReplier.getNextFillRequest(); 392 393 // Dataset should still be shown 394 mUiBot.assertDatasets("dataset2"); 395 } 396 397 @Test testAutofillNestedActivitiesWhileAutofilledAppIsLifecycled()398 public void testAutofillNestedActivitiesWhileAutofilledAppIsLifecycled() throws Exception { 399 // Set service. 400 enableService(); 401 402 // Start activity that is autofilled in a separate process so it can be killed 403 startAndWaitExternalActivity(); 404 405 // Prepare response for first activity 406 CannedFillResponse response = new CannedFillResponse.Builder() 407 .addDataset(new CannedFillResponse.CannedDataset.Builder( 408 createPresentation("dataset1")) 409 .setField(ID_USERNAME, "filled").build()) 410 .build(); 411 sReplier.addResponse(response); 412 413 // Trigger autofill on username 414 mUiBot.selectByRelativeId(ID_USERNAME); 415 416 // Wait for fill request to be processed 417 sReplier.getNextFillRequest(); 418 419 // Wait until dataset1 is shown 420 mUiBot.assertDatasets("dataset1"); 421 422 // Delete stopped marker 423 getStoppedMarker(getContext()).delete(); 424 425 // Prepare response for nested activity 426 response = new CannedFillResponse.Builder() 427 .addDataset(new CannedFillResponse.CannedDataset.Builder( 428 createPresentation("dataset2")) 429 .setField(ID_USERNAME, "filled").build()) 430 .build(); 431 sReplier.addResponse(response); 432 433 // Start nested login activity 434 Intent intent = new Intent(getContext(), LoginActivity.class); 435 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 436 getContext().startActivity(intent); 437 438 // Kill activity that is in the background 439 killOfProcessLoginActivityProcess(); 440 441 // Trigger autofill on username in nested activity 442 mUiBot.selectByRelativeId(ID_USERNAME); 443 444 // Wait for fill request to be processed 445 sReplier.getNextFillRequest(); 446 447 // Wait until dataset in nested activity is shown 448 mUiBot.assertDatasets("dataset2"); 449 450 // Set expectations for back to the first activity 451 sReplier.addResponse( 452 new CannedFillResponse.Builder() 453 .addDataset(new CannedFillResponse.CannedDataset.Builder( 454 createPresentation("dataset3")) 455 .setField(ID_USERNAME, "filled").build()) 456 .build()); 457 458 // Tap "Cancel". 459 mUiBot.selectByRelativeId(ID_CANCEL); 460 461 // Wait for fill request to be processed 462 mUiBot.waitForIdle(); 463 sReplier.getNextFillRequest(); 464 465 // Dataset should still be shown 466 mUiBot.assertDatasets("dataset3"); 467 } 468 469 @Test testDatasetGoesAwayWhenAutofilledAppIsKilled()470 public void testDatasetGoesAwayWhenAutofilledAppIsKilled() throws Exception { 471 // Set service. 472 enableService(); 473 474 // Start activity that is autofilled in a separate process so it can be killed 475 startAndWaitExternalActivity(); 476 477 final CannedFillResponse response = new CannedFillResponse.Builder() 478 .addDataset(new CannedFillResponse.CannedDataset.Builder( 479 createPresentation("dataset")) 480 .setField(ID_USERNAME, "filled").build()) 481 .build(); 482 sReplier.addResponse(response); 483 484 // Trigger autofill on username 485 mUiBot.selectByRelativeId(ID_USERNAME); 486 487 // Wait for fill request to be processed 488 sReplier.getNextFillRequest(); 489 490 // Wait until dataset is shown 491 mUiBot.assertDatasets("dataset"); 492 493 // Kill activity 494 killOfProcessLoginActivityProcess(); 495 496 // Make sure dataset is not shown anymore 497 mUiBot.assertNoDatasetsEver(); 498 499 // Restart activity an make sure the dataset is still not shown 500 startAndWaitExternalActivity(); 501 mUiBot.assertNoDatasets(); 502 } 503 504 @Test testSaveRemainsWhenAutofilledAppIsKilled()505 public void testSaveRemainsWhenAutofilledAppIsKilled() throws Exception { 506 // Set service. 507 enableService(); 508 509 // Start activity that is autofilled in a separate process so it can be killed 510 startAndWaitExternalActivity(); 511 512 final CannedFillResponse response = new CannedFillResponse.Builder() 513 .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME) 514 .build(); 515 sReplier.addResponse(response); 516 517 // Trigger autofill on username 518 mUiBot.selectByRelativeId(ID_USERNAME); 519 520 // Wait for fill request to be processed 521 sReplier.getNextFillRequest(); 522 523 // Wait until dataset is shown 524 mUiBot.assertNoDatasetsEver(); 525 526 // Trigger save 527 mUiBot.setTextByRelativeId(ID_USERNAME, "dude"); 528 mUiBot.selectByRelativeId(ID_LOGIN); 529 mUiBot.assertSaveShowing(SAVE_DATA_TYPE_USERNAME); 530 531 // Kill activity 532 killOfProcessLoginActivityProcess(); 533 534 // Make sure save is still showing 535 final UiObject2 saveSnackBar = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_USERNAME); 536 537 mUiBot.saveForAutofill(saveSnackBar, true); 538 539 final InstrumentedAutoFillService.SaveRequest saveRequest = sReplier.getNextSaveRequest(); 540 541 // Make sure data is correctly saved 542 final AssistStructure.ViewNode username = findNodeByResourceId(saveRequest.structure, 543 ID_USERNAME); 544 assertTextAndValue(username, "dude"); 545 } 546 } 547