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