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 package android.autofillservice.cts; 17 18 import static com.google.common.truth.Truth.assertWithMessage; 19 20 import android.content.Context; 21 import android.content.Intent; 22 import android.os.Bundle; 23 import android.util.Log; 24 import android.view.View; 25 import android.view.View.OnClickListener; 26 import android.view.inputmethod.InputMethodManager; 27 import android.widget.Button; 28 import android.widget.EditText; 29 import android.widget.TextView; 30 31 import java.util.concurrent.CountDownLatch; 32 import java.util.concurrent.TimeUnit; 33 34 /** 35 * Activity that has the following fields: 36 * 37 * <ul> 38 * <li>Username EditText (id: username, no input-type) 39 * <li>Password EditText (id: "username", input-type textPassword) 40 * <li>Clear Button 41 * <li>Save Button 42 * <li>Login Button 43 * </ul> 44 */ 45 public class LoginActivity extends AbstractAutoFillActivity { 46 47 private static final String TAG = "LoginActivity"; 48 private static String WELCOME_TEMPLATE = "Welcome to the new activity, %s!"; 49 private static final long LOGIN_TIMEOUT_MS = 1000; 50 51 static final String ID_USERNAME_CONTAINER = "username_container"; 52 static final String AUTHENTICATION_MESSAGE = "Authentication failed. D'OH!"; 53 54 private TextView mUsernameLabel; 55 private EditText mUsernameEditText; 56 private TextView mPasswordLabel; 57 private EditText mPasswordEditText; 58 private TextView mOutput; 59 private Button mLoginButton; 60 private Button mSaveButton; 61 private Button mCancelButton; 62 private Button mClearButton; 63 private FillExpectation mExpectation; 64 65 // State used to synchronously get the result of a login attempt. 66 private CountDownLatch mLoginLatch; 67 private String mLoginMessage; 68 69 /** 70 * Gets the expected welcome message for a given username. 71 */ getWelcomeMessage(String username)72 static String getWelcomeMessage(String username) { 73 return String.format(WELCOME_TEMPLATE, username); 74 } 75 76 @Override onCreate(Bundle savedInstanceState)77 protected void onCreate(Bundle savedInstanceState) { 78 super.onCreate(savedInstanceState); 79 80 setContentView(getContentView()); 81 82 mLoginButton = (Button) findViewById(R.id.login); 83 mSaveButton = (Button) findViewById(R.id.save); 84 mClearButton = (Button) findViewById(R.id.clear); 85 mCancelButton = (Button) findViewById(R.id.cancel); 86 mUsernameLabel = (TextView) findViewById(R.id.username_label); 87 mUsernameEditText = (EditText) findViewById(R.id.username); 88 mPasswordLabel = (TextView) findViewById(R.id.password_label); 89 mPasswordEditText = (EditText) findViewById(R.id.password); 90 mOutput = (TextView) findViewById(R.id.output); 91 92 mLoginButton.setOnClickListener((v) -> login()); 93 mSaveButton.setOnClickListener((v) -> save()); 94 mClearButton.setOnClickListener((v) -> { 95 mUsernameEditText.setText(""); 96 mPasswordEditText.setText(""); 97 mOutput.setText(""); 98 getAutofillManager().cancel(); 99 }); 100 mCancelButton.setOnClickListener((OnClickListener) v -> finish()); 101 } 102 getContentView()103 protected int getContentView() { 104 return R.layout.login_activity; 105 } 106 107 /** 108 * Emulates a login action. 109 */ login()110 private void login() { 111 final String username = mUsernameEditText.getText().toString(); 112 final String password = mPasswordEditText.getText().toString(); 113 final boolean valid = username.equals(password) || password.contains("pass"); 114 115 if (valid) { 116 Log.d(TAG, "login ok: " + username); 117 final Intent intent = new Intent(this, WelcomeActivity.class); 118 final String message = getWelcomeMessage(username); 119 intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, message); 120 setLoginMessage(message); 121 startActivity(intent); 122 finish(); 123 } else { 124 Log.d(TAG, "login failed: " + AUTHENTICATION_MESSAGE); 125 mOutput.setText(AUTHENTICATION_MESSAGE); 126 setLoginMessage(AUTHENTICATION_MESSAGE); 127 } 128 } 129 setLoginMessage(String message)130 private void setLoginMessage(String message) { 131 Log.d(TAG, "setLoginMessage(): " + message); 132 if (mLoginLatch != null) { 133 mLoginMessage = message; 134 mLoginLatch.countDown(); 135 } 136 } 137 138 /** 139 * Explicitly forces the AutofillManager to save the username and password. 140 */ save()141 private void save() { 142 final InputMethodManager imm = (InputMethodManager) getSystemService( 143 Context.INPUT_METHOD_SERVICE); 144 imm.hideSoftInputFromWindow(mUsernameEditText.getWindowToken(), 0); 145 getAutofillManager().commit(); 146 } 147 148 /** 149 * Sets the expectation for an autofill request (for all fields), so it can be asserted through 150 * {@link #assertAutoFilled()} later. 151 */ expectAutoFill(String username, String password)152 void expectAutoFill(String username, String password) { 153 mExpectation = new FillExpectation(username, password); 154 mUsernameEditText.addTextChangedListener(mExpectation.ccUsernameWatcher); 155 mPasswordEditText.addTextChangedListener(mExpectation.ccPasswordWatcher); 156 } 157 158 /** 159 * Sets the expectation for an autofill request (for username only), so it can be asserted 160 * through {@link #assertAutoFilled()} later. 161 */ expectAutoFill(String username)162 void expectAutoFill(String username) { 163 mExpectation = new FillExpectation(username); 164 mUsernameEditText.addTextChangedListener(mExpectation.ccUsernameWatcher); 165 } 166 167 /** 168 * Asserts the activity was auto-filled with the values passed to 169 * {@link #expectAutoFill(String, String)}. 170 */ assertAutoFilled()171 void assertAutoFilled() throws Exception { 172 assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull(); 173 mExpectation.ccUsernameWatcher.assertAutoFilled(); 174 if (mExpectation.ccPasswordWatcher != null) { 175 mExpectation.ccPasswordWatcher.assertAutoFilled(); 176 } 177 } 178 forceAutofillOnUsername()179 void forceAutofillOnUsername() { 180 syncRunOnUiThread(() -> getAutofillManager().requestAutofill(mUsernameEditText)); 181 } 182 183 /** 184 * Visits the {@code username_label} in the UiThread. 185 */ onUsernameLabel(Visitor<TextView> v)186 void onUsernameLabel(Visitor<TextView> v) { 187 syncRunOnUiThread(() -> v.visit(mUsernameLabel)); 188 } 189 190 /** 191 * Visits the {@code username} in the UiThread. 192 */ onUsername(Visitor<EditText> v)193 void onUsername(Visitor<EditText> v) { 194 syncRunOnUiThread(() -> v.visit(mUsernameEditText)); 195 } 196 197 /** 198 * Gets the {@code username} view. 199 */ getUsername()200 EditText getUsername() { 201 return mUsernameEditText; 202 } 203 204 /** 205 * Visits the {@code password_label} in the UiThread. 206 */ onPasswordLabel(Visitor<TextView> v)207 void onPasswordLabel(Visitor<TextView> v) { 208 syncRunOnUiThread(() -> v.visit(mPasswordLabel)); 209 } 210 211 /** 212 * Visits the {@code password} in the UiThread. 213 */ onPassword(Visitor<EditText> v)214 void onPassword(Visitor<EditText> v) { 215 syncRunOnUiThread(() -> v.visit(mPasswordEditText)); 216 } 217 218 /** 219 * Gets the {@code password} view. 220 */ getPassword()221 EditText getPassword() { 222 return mPasswordEditText; 223 } 224 225 /** 226 * Taps the login button in the UI thread. 227 */ tapLogin()228 String tapLogin() throws Exception { 229 mLoginLatch = new CountDownLatch(1); 230 syncRunOnUiThread(() -> mLoginButton.performClick()); 231 boolean called = mLoginLatch.await(LOGIN_TIMEOUT_MS, TimeUnit.MILLISECONDS); 232 assertWithMessage("Timeout (%s ms) waiting for login", LOGIN_TIMEOUT_MS) 233 .that(called).isTrue(); 234 return mLoginMessage; 235 } 236 237 /** 238 * Taps the save button in the UI thread. 239 */ tapSave()240 void tapSave() throws Exception { 241 syncRunOnUiThread(() -> mSaveButton.performClick()); 242 } 243 244 /** 245 * Taps the clear button in the UI thread. 246 */ tapClear()247 public void tapClear() { 248 syncRunOnUiThread(() -> mClearButton.performClick()); 249 } 250 251 /** 252 * Sets the window flags. 253 */ setFlags(int flags)254 void setFlags(int flags) { 255 Log.d(TAG, "setFlags():" + flags); 256 syncRunOnUiThread(() -> getWindow().setFlags(flags, flags)); 257 } 258 259 /** 260 * Holder for the expected auto-fill values. 261 */ 262 private final class FillExpectation { 263 private final OneTimeTextWatcher ccUsernameWatcher; 264 private final OneTimeTextWatcher ccPasswordWatcher; 265 FillExpectation(String username, String password)266 private FillExpectation(String username, String password) { 267 ccUsernameWatcher = new OneTimeTextWatcher("username", mUsernameEditText, username); 268 ccPasswordWatcher = new OneTimeTextWatcher("password", mPasswordEditText, password); 269 } 270 FillExpectation(String username)271 private FillExpectation(String username) { 272 ccUsernameWatcher = new OneTimeTextWatcher("username", mUsernameEditText, username); 273 ccPasswordWatcher = null; 274 } 275 } 276 } 277