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 android.widget.ArrayAdapter.createFromResource; 19 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import android.content.Intent; 23 import android.os.Bundle; 24 import android.widget.ArrayAdapter; 25 import android.widget.Button; 26 import android.widget.CheckBox; 27 import android.widget.EditText; 28 import android.widget.RadioButton; 29 import android.widget.RadioGroup; 30 import android.widget.Spinner; 31 32 import java.util.concurrent.CountDownLatch; 33 import java.util.concurrent.TimeUnit; 34 35 /** 36 * Activity that has the following fields: 37 * 38 * <ul> 39 * <li>Credit Card Number EditText (id: cc_numberusername, no input-type) 40 * <li>Credit Card Expiration EditText (id: cc_expiration, no input-type) 41 * <li>Address RadioGroup (id: addess, no autofill-type) 42 * <li>Save Credit Card CheckBox (id: save_cc, no autofill-type) 43 * <li>Clear Button 44 * <li>Buy Button 45 * </ul> 46 */ 47 public class CheckoutActivity extends AbstractAutoFillActivity { 48 private static final long BUY_TIMEOUT_MS = 1000; 49 50 static final String ID_CC_NUMBER = "cc_number"; 51 static final String ID_CC_EXPIRATION = "cc_expiration"; 52 static final String ID_ADDRESS = "address"; 53 static final String ID_HOME_ADDRESS = "home_address"; 54 static final String ID_WORK_ADDRESS = "work_address"; 55 static final String ID_SAVE_CC = "save_cc"; 56 57 static final int INDEX_ADDRESS_HOME = 0; 58 static final int INDEX_ADDRESS_WORK = 1; 59 60 static final int INDEX_CC_EXPIRATION_YESTERDAY = 0; 61 static final int INDEX_CC_EXPIRATION_TODAY = 1; 62 static final int INDEX_CC_EXPIRATION_TOMORROW = 2; 63 static final int INDEX_CC_EXPIRATION_NEVER = 3; 64 65 private EditText mCcNumber; 66 private Spinner mCcExpiration; 67 private ArrayAdapter<CharSequence> mCcExpirationAdapter; 68 private RadioGroup mAddress; 69 private RadioButton mHomeAddress; 70 private CheckBox mSaveCc; 71 private Button mBuyButton; 72 private Button mClearButton; 73 74 private FillExpectation mExpectation; 75 private CountDownLatch mBuyLatch; 76 77 @Override onCreate(Bundle savedInstanceState)78 protected void onCreate(Bundle savedInstanceState) { 79 super.onCreate(savedInstanceState); 80 81 setContentView(getContentView()); 82 83 mCcNumber = (EditText) findViewById(R.id.cc_number); 84 mCcExpiration = (Spinner) findViewById(R.id.cc_expiration); 85 mAddress = (RadioGroup) findViewById(R.id.address); 86 mHomeAddress = (RadioButton ) findViewById(R.id.home_address); 87 mSaveCc = (CheckBox) findViewById(R.id.save_cc); 88 mBuyButton = (Button) findViewById(R.id.buy); 89 mClearButton = (Button) findViewById(R.id.clear); 90 91 mCcExpirationAdapter = createFromResource(this, 92 R.array.cc_expiration_values, android.R.layout.simple_spinner_item); 93 mCcExpirationAdapter 94 .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 95 mCcExpiration.setAdapter(mCcExpirationAdapter); 96 97 mBuyButton.setOnClickListener((v) -> buy()); 98 mClearButton.setOnClickListener((v) -> resetFields()); 99 } 100 getContentView()101 protected int getContentView() { 102 return R.layout.checkout_activity; 103 } 104 105 /** 106 * Resets the values of the input fields. 107 */ resetFields()108 private void resetFields() { 109 mCcNumber.setText(""); 110 mCcExpiration.setSelection(0, false); 111 mAddress.clearCheck(); 112 mSaveCc.setChecked(false); 113 } 114 115 /** 116 * Emulates a buy action. 117 */ buy()118 private void buy() { 119 final Intent intent = new Intent(this, WelcomeActivity.class); 120 intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "Thank you an come again!"); 121 startActivity(intent); 122 if (mBuyLatch != null) { 123 // Latch is not set when activity launched outside tests 124 mBuyLatch.countDown(); 125 } 126 finish(); 127 } 128 129 /** 130 * Sets the expectation for an auto-fill request, so it can be asserted through 131 * {@link #assertAutoFilled()} later. 132 */ expectAutoFill(String ccNumber, int ccExpirationIndex, int addressId, boolean saveCc)133 void expectAutoFill(String ccNumber, int ccExpirationIndex, int addressId, boolean saveCc) { 134 mExpectation = new FillExpectation(ccNumber, ccExpirationIndex, addressId, saveCc); 135 mCcNumber.addTextChangedListener(mExpectation.ccNumberWatcher); 136 mCcExpiration.setOnItemSelectedListener(mExpectation.ccExpirationListener); 137 mAddress.setOnCheckedChangeListener(mExpectation.addressListener); 138 mSaveCc.setOnCheckedChangeListener(mExpectation.saveCcListener); 139 } 140 141 /** 142 * Asserts the activity was auto-filled with the values passed to 143 * {@link #expectAutoFill(String, int, int, boolean)}. 144 */ assertAutoFilled()145 void assertAutoFilled() throws Exception { 146 assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull(); 147 mExpectation.ccNumberWatcher.assertAutoFilled(); 148 mExpectation.ccExpirationListener.assertAutoFilled(); 149 mExpectation.addressListener.assertAutoFilled(); 150 mExpectation.saveCcListener.assertAutoFilled(); 151 } 152 153 /** 154 * Visits the {@code ccNumber} in the UiThread. 155 */ onCcNumber(Visitor<EditText> v)156 void onCcNumber(Visitor<EditText> v) { 157 syncRunOnUiThread(() -> v.visit(mCcNumber)); 158 } 159 160 /** 161 * Visits the {@code ccExpirationDate} in the UiThread. 162 */ onCcExpiration(Visitor<Spinner> v)163 void onCcExpiration(Visitor<Spinner> v) { 164 syncRunOnUiThread(() -> v.visit(mCcExpiration)); 165 } 166 167 /** 168 * Visits the {@code ccExpirationDate} adapter in the UiThread. 169 */ onCcExpirationAdapter(Visitor<ArrayAdapter<CharSequence>> v)170 void onCcExpirationAdapter(Visitor<ArrayAdapter<CharSequence>> v) { 171 syncRunOnUiThread(() -> v.visit(mCcExpirationAdapter)); 172 } 173 174 /** 175 * Visits the {@code address} in the UiThread. 176 */ onAddress(Visitor<RadioGroup> v)177 void onAddress(Visitor<RadioGroup> v) { 178 syncRunOnUiThread(() -> v.visit(mAddress)); 179 } 180 181 /** 182 * Visits the {@code homeAddress} in the UiThread. 183 */ onHomeAddress(Visitor<RadioButton> v)184 void onHomeAddress(Visitor<RadioButton> v) { 185 syncRunOnUiThread(() -> v.visit(mHomeAddress)); 186 } 187 188 /** 189 * Visits the {@code saveCC} in the UiThread. 190 */ onSaveCc(Visitor<CheckBox> v)191 void onSaveCc(Visitor<CheckBox> v) { 192 syncRunOnUiThread(() -> v.visit(mSaveCc)); 193 } 194 195 /** 196 * Taps the buy button in the UI thread. 197 */ tapBuy()198 void tapBuy() throws Exception { 199 mBuyLatch = new CountDownLatch(1); 200 syncRunOnUiThread(() -> mBuyButton.performClick()); 201 boolean called = mBuyLatch.await(BUY_TIMEOUT_MS, TimeUnit.MILLISECONDS); 202 assertWithMessage("Timeout (%s ms) waiting for buy action", BUY_TIMEOUT_MS) 203 .that(called).isTrue(); 204 } 205 206 /** 207 * Holder for the expected auto-fill values. 208 */ 209 private final class FillExpectation { 210 private final OneTimeTextWatcher ccNumberWatcher; 211 private final OneTimeSpinnerListener ccExpirationListener; 212 private final OneTimeRadioGroupListener addressListener; 213 private final OneTimeCompoundButtonListener saveCcListener; 214 FillExpectation(String ccNumber, int ccExpirationIndex, int addressId, boolean saveCc)215 private FillExpectation(String ccNumber, int ccExpirationIndex, int addressId, 216 boolean saveCc) { 217 this.ccNumberWatcher = new OneTimeTextWatcher("ccNumber", mCcNumber, ccNumber); 218 this.ccExpirationListener = 219 new OneTimeSpinnerListener("ccExpiration", mCcExpiration, ccExpirationIndex); 220 addressListener = new OneTimeRadioGroupListener("address", mAddress, addressId); 221 saveCcListener = new OneTimeCompoundButtonListener("saveCc", mSaveCc, saveCc); 222 } 223 } 224 } 225