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