1 /* 2 * Copyright (C) 2018 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 package android.autofillservice.cts.saveui; 17 18 import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_INPUT; 19 import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_PASSWORD; 20 import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_HIDE; 21 import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_PASSWORD_MASKED; 22 import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_PASSWORD_PLAIN; 23 import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_SHOW; 24 import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_USERNAME_MASKED; 25 import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_USERNAME_PLAIN; 26 import static android.autofillservice.cts.testcore.CustomDescriptionHelper.newCustomDescriptionWithHiddenFields; 27 import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL; 28 import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL; 29 import static android.autofillservice.cts.testcore.Helper.assertTextAndValue; 30 import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId; 31 import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId; 32 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC; 33 34 import android.autofillservice.cts.R; 35 import android.autofillservice.cts.activities.SimpleSaveActivity; 36 import android.autofillservice.cts.commontests.AutoFillServiceTestCase; 37 import android.autofillservice.cts.testcore.AutofillActivityTestRule; 38 import android.autofillservice.cts.testcore.CannedFillResponse; 39 import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest; 40 import android.autofillservice.cts.testcore.Timeouts; 41 import android.platform.test.annotations.AppModeFull; 42 import android.service.autofill.CharSequenceTransformation; 43 import android.service.autofill.FillContext; 44 import android.service.autofill.OnClickAction; 45 import android.service.autofill.VisibilitySetterAction; 46 import android.view.View; 47 import android.view.autofill.AutofillId; 48 49 import androidx.test.uiautomator.UiObject2; 50 51 import org.junit.Test; 52 53 import java.util.regex.Pattern; 54 55 /** 56 * Integration tests for the {@link OnClickAction} implementations. 57 */ 58 @AppModeFull(reason = "Service-specific test") 59 public class OnClickActionTest 60 extends AutoFillServiceTestCase.AutoActivityLaunch<SimpleSaveActivity> { 61 62 private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$"); 63 64 private SimpleSaveActivity mActivity; 65 66 @Override getActivityRule()67 protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() { 68 return new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class) { 69 @Override 70 protected void afterActivityLaunched() { 71 mActivity = getActivity(); 72 } 73 }; 74 } 75 76 @Test 77 public void testHideAndShow() throws Exception { 78 // Set service. 79 enableService(); 80 81 // Set expectations. 82 sReplier.addResponse(new CannedFillResponse.Builder() 83 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 84 .setSaveInfoVisitor((contexts, builder) -> { 85 final FillContext context = contexts.get(0); 86 final AutofillId usernameId = findAutofillIdByResourceId(context, ID_INPUT); 87 final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD); 88 89 final CharSequenceTransformation usernameTrans = new CharSequenceTransformation 90 .Builder(usernameId, MATCH_ALL, "$1").build(); 91 final CharSequenceTransformation passwordTrans = new CharSequenceTransformation 92 .Builder(passwordId, MATCH_ALL, "$1").build(); 93 builder.setCustomDescription(newCustomDescriptionWithHiddenFields() 94 .addChild(R.id.username_plain, usernameTrans) 95 .addChild(R.id.password_plain, passwordTrans) 96 .addOnClickAction(R.id.show, new VisibilitySetterAction 97 .Builder(R.id.hide, View.VISIBLE) 98 .setVisibility(R.id.show, View.GONE) 99 .setVisibility(R.id.username_plain, View.VISIBLE) 100 .setVisibility(R.id.password_plain, View.VISIBLE) 101 .setVisibility(R.id.username_masked, View.GONE) 102 .setVisibility(R.id.password_masked, View.GONE) 103 .build()) 104 .addOnClickAction(R.id.hide, new VisibilitySetterAction 105 .Builder(R.id.show, View.VISIBLE) 106 .setVisibility(R.id.hide, View.GONE) 107 .setVisibility(R.id.username_masked, View.VISIBLE) 108 .setVisibility(R.id.password_masked, View.VISIBLE) 109 .setVisibility(R.id.username_plain, View.GONE) 110 .setVisibility(R.id.password_plain, View.GONE) 111 .build()) 112 .build()); 113 }) 114 .build()); 115 116 // Trigger autofill. 117 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 118 sReplier.getNextFillRequest(); 119 120 // Trigger save. 121 mActivity.setTextAndWaitTextChange(/* input= */ "42", /* password= */ "108"); 122 mActivity.syncRunOnUiThread(() -> { 123 mActivity.mCommit.performClick(); 124 }); 125 final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 126 127 // Assert initial UI is hidden the password. 128 final UiObject2 showButton = assertHidden(saveUi); 129 130 // Then tap SHOW and assert it's showing how 131 showButton.click(); 132 final UiObject2 hideButton = assertShown(saveUi); 133 134 // Hide again 135 hideButton.click(); 136 assertHidden(saveUi); 137 138 // Rinse-and repeat a couple times 139 showButton.click(); assertShown(saveUi); 140 hideButton.click(); assertHidden(saveUi); 141 showButton.click(); assertShown(saveUi); 142 hideButton.click(); assertHidden(saveUi); 143 144 // Then save it... 145 mUiBot.saveForAutofill(saveUi, true); 146 147 // ... and assert results 148 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 149 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42"); 150 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "108"); 151 } 152 153 /** 154 * Asserts that the Save UI is in the hiding the password field, returning the {@code SHOW} 155 * button. 156 */ 157 private UiObject2 assertHidden(UiObject2 saveUi) throws Exception { 158 // Username 159 mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:"); 160 mUiBot.assertChildText(saveUi, ID_USERNAME_MASKED, "****"); 161 assertInvisible(saveUi, ID_USERNAME_PLAIN); 162 163 // Password 164 mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:"); 165 mUiBot.assertChildText(saveUi, ID_PASSWORD_MASKED, "...."); 166 assertInvisible(saveUi, ID_PASSWORD_PLAIN); 167 168 // Buttons 169 assertInvisible(saveUi, ID_HIDE); 170 return mUiBot.assertChildText(saveUi, ID_SHOW, "SHOW"); 171 } 172 173 /** 174 * Asserts that the Save UI is in the showing the password field, returning the {@code HIDE} 175 * button. 176 */ 177 private UiObject2 assertShown(UiObject2 saveUi) throws Exception { 178 // Username 179 mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:"); 180 mUiBot.assertChildText(saveUi, ID_USERNAME_PLAIN, "42"); 181 assertInvisible(saveUi, ID_USERNAME_MASKED); 182 183 // Password 184 mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:"); 185 mUiBot.assertChildText(saveUi, ID_PASSWORD_PLAIN, "108"); 186 assertInvisible(saveUi, ID_PASSWORD_MASKED); 187 188 // Buttons 189 assertInvisible(saveUi, ID_SHOW); 190 return mUiBot.assertChildText(saveUi, ID_HIDE, "HIDE"); 191 } 192 193 private void assertInvisible(UiObject2 saveUi, String resourceId) { 194 mUiBot.assertGoneByRelativeId(saveUi, resourceId, Timeouts.UI_TIMEOUT); 195 } 196 } 197