• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.CannedFillResponse.NO_RESPONSE;
20 import static android.autofillservice.cts.DuplicateIdActivity.DUPLICATE_ID;
21 import static android.autofillservice.cts.Helper.assertEqualsIgnoreSession;
22 
23 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
24 
25 import static com.google.common.truth.Truth.assertThat;
26 
27 import static org.junit.Assume.assumeTrue;
28 
29 import android.app.assist.AssistStructure;
30 import android.app.assist.AssistStructure.ViewNode;
31 import android.util.Log;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.view.autofill.AutofillId;
35 import android.widget.EditText;
36 
37 import org.junit.After;
38 import org.junit.Before;
39 import org.junit.Test;
40 
41 public class DuplicateIdActivityTest
42         extends AutoFillServiceTestCase.AutoActivityLaunch<DuplicateIdActivity> {
43 
44     private static final String TAG = "DuplicateIdActivityTest";
45 
46     private static final AutofillActivityTestRule<DuplicateIdActivity> sActivityRule =
47             new AutofillActivityTestRule<DuplicateIdActivity>(DuplicateIdActivity.class);
48 
49     @Override
getActivityRule()50     protected AutofillActivityTestRule<DuplicateIdActivity> getActivityRule() {
51         return sActivityRule;
52     }
53 
54     @Before
setup()55     public void setup() throws Exception {
56         Helper.disableAutoRotation(mUiBot);
57         mUiBot.setScreenOrientation(0);
58     }
59 
60     @After
teardown()61     public void teardown() {
62         Helper.allowAutoRotation();
63     }
64 
65     /**
66      * Find the views that are tested from the structure in the request
67      *
68      * @param request The request
69      * @param expectedCount The expected number of children
70      *
71      * @return An array containing the two tested views
72      */
findViews(InstrumentedAutoFillService.FillRequest request, int expectedCount)73     private AssistStructure.ViewNode[] findViews(InstrumentedAutoFillService.FillRequest request,
74             int expectedCount) {
75         assertThat(request.structure.getWindowNodeCount()).isEqualTo(1);
76         AssistStructure.WindowNode windowNode = request.structure.getWindowNodeAt(0);
77 
78         AssistStructure.ViewNode rootNode = windowNode.getRootViewNode();
79 
80         assertThat(rootNode.getChildCount()).isEqualTo(expectedCount);
81         final ViewNode[] viewNodes = new AssistStructure.ViewNode[expectedCount];
82         for (int i = 0; i < expectedCount; i++) {
83             viewNodes[i] = rootNode.getChildAt(i);
84         }
85         return viewNodes;
86     }
87 
88     @Test
testDoNotRestoreDuplicateAutofillIds()89     public void testDoNotRestoreDuplicateAutofillIds() throws Exception {
90         assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
91 
92         enableService();
93 
94         sReplier.addResponse(new CannedFillResponse.Builder()
95                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
96                         .setField(DUPLICATE_ID, "value")
97                         .setPresentation(createPresentation("dataset"))
98                         .build())
99                 .build());
100 
101         // Select field to start autofill
102         runShellCommand("input keyevent KEYCODE_TAB");
103 
104         final InstrumentedAutoFillService.FillRequest request1 = sReplier.getNextFillRequest();
105         Log.v(TAG, "request1: " + request1);
106 
107         final AssistStructure.ViewNode[] views1 = findViews(request1, 2);
108         final AssistStructure.ViewNode view1 = views1[0];
109         final AssistStructure.ViewNode view2 = views1[1];
110         final AutofillId id1 = view1.getAutofillId();
111         final AutofillId id2 = view2.getAutofillId();
112 
113         Log.v(TAG, "view1=" + id1);
114         Log.v(TAG, "view2=" + id2);
115 
116         // Both checkboxes use the same id
117         assertThat(view1.getId()).isEqualTo(view2.getId());
118 
119         // They got different autofill ids though
120         assertThat(id1).isNotEqualTo(id2);
121 
122         // Because service returned a null response, rotation will trigger another request.
123         sReplier.addResponse(NO_RESPONSE);
124         // Force rotation to force onDestroy->onCreate cycle
125         mUiBot.setScreenOrientation(1);
126         // Wait context and Views being recreated in rotation
127         mUiBot.assertShownByRelativeId(DUPLICATE_ID);
128         // Ignore 2nd request.
129         final InstrumentedAutoFillService.FillRequest request2 = sReplier.getNextFillRequest();
130         Log.v(TAG, "request2: " + request2);
131 
132         // Select a new field to trigger new partition (because server return null on 1st response)
133         sReplier.addResponse(NO_RESPONSE);
134         final DuplicateIdActivity activity = getActivity();
135         final EditText child = new EditText(activity);
136         activity.syncRunOnUiThread(() -> {
137             final View sibling = activity.findViewById(R.id.duplicate_id);
138             final ViewGroup parent = (ViewGroup) sibling.getParent();
139             parent.addView(child);
140             child.requestFocus();
141         });
142 
143         final InstrumentedAutoFillService.FillRequest request3 = sReplier.getNextFillRequest();
144         Log.v(TAG, "request3: " + request3);
145         final AssistStructure.ViewNode[] views2 = findViews(request3, 3);
146         final AssistStructure.ViewNode recreatedView1 = views2[0];
147         final AssistStructure.ViewNode recreatedView2 = views2[1];
148         final AssistStructure.ViewNode newView1 = views2[2];
149         final AutofillId recreatedId1 = recreatedView1.getAutofillId();
150         final AutofillId recreatedId2 = recreatedView2.getAutofillId();
151         final AutofillId newId1 = newView1.getAutofillId();
152 
153         Log.v(TAG, "restored view1=" + recreatedId1);
154         Log.v(TAG, "restored view2=" + recreatedId2);
155         Log.v(TAG, "new view1=" + newId1);
156 
157         // For the restoring logic the two views are the same. Hence it might happen that the first
158         // view is restored with the autofill id of the second view or the other way round.
159         // We just need
160         // - to restore as many views as we can (i.e. one)
161         // - make sure the autofill ids are still unique after
162         final boolean view1WasRestored = (recreatedId1.equalsIgnoreSession(id1)
163                 || recreatedId1.equalsIgnoreSession(id2));
164         final boolean view2WasRestored = (recreatedId2.equalsIgnoreSession(id1)
165                 || recreatedId2.equalsIgnoreSession(id2));
166 
167         // One id was restored
168         assertThat(view1WasRestored || view2WasRestored).isTrue();
169 
170         // The views still have different autofill ids
171         assertThat(recreatedId1).isNotEqualTo(recreatedId2);
172 
173         // Assert id of new view
174         assertEqualsIgnoreSession(newId1, child.getAutofillId());
175     }
176 }
177