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