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