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