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