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