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 android.os.Bundle;
19 import android.util.Log;
20 import android.view.autofill.AutofillManager;
21 import android.widget.Button;
22 import android.widget.EditText;
23 import android.widget.GridLayout;
24 
25 import java.util.ArrayList;
26 import java.util.concurrent.BlockingQueue;
27 import java.util.concurrent.LinkedBlockingQueue;
28 import java.util.concurrent.TimeUnit;
29 
30 /**
31  * Activity that contains a 4x4 grid of cells (named {@code l1c1} to {@code l4c2}) plus
32  * {@code save} and {@code clear} buttons.
33  */
34 public class GridActivity extends AbstractAutoFillActivity {
35 
36     private static final String TAG = "GridActivity";
37     private static final int N_ROWS = 4;
38     private static final int N_COLS = 2;
39 
40     public static final String ID_L1C1 = getResourceId(1, 1);
41     public static final String ID_L1C2 = getResourceId(1, 2);
42     public static final String ID_L2C1 = getResourceId(2, 1);
43     public static final String ID_L2C2 = getResourceId(2, 2);
44     public static final String ID_L3C1 = getResourceId(3, 1);
45     public static final String ID_L3C2 = getResourceId(3, 2);
46     public static final String ID_L4C1 = getResourceId(4, 1);
47     public static final String ID_L4C2 = getResourceId(4, 2);
48 
49     private GridLayout mGrid;
50     private final EditText[][] mCells = new EditText[4][2];
51     private Button mSaveButton;
52     private Button mClearButton;
53 
54     @Override
onCreate(Bundle savedInstanceState)55     protected void onCreate(Bundle savedInstanceState) {
56         super.onCreate(savedInstanceState);
57 
58         setContentView(R.layout.grid_activity);
59 
60         mGrid = findViewById(R.id.grid);
61         mCells[0][0] = findViewById(R.id.l1c1);
62         mCells[0][1] = findViewById(R.id.l1c2);
63         mCells[1][0] = findViewById(R.id.l2c1);
64         mCells[1][1] = findViewById(R.id.l2c2);
65         mCells[2][0] = findViewById(R.id.l3c1);
66         mCells[2][1] = findViewById(R.id.l3c2);
67         mCells[3][0] = findViewById(R.id.l4c1);
68         mCells[3][1] = findViewById(R.id.l4c2);
69         mSaveButton = findViewById(R.id.save);
70         mClearButton = findViewById(R.id.clear);
71 
72         mSaveButton.setOnClickListener((v) -> save());
73         mClearButton.setOnClickListener((v) -> resetFields());
74     }
75 
save()76     void save() {
77         getSystemService(AutofillManager.class).commit();
78     }
79 
resetFields()80     void resetFields() {
81         for (int i = 0; i < N_ROWS; i++) {
82             for (int j = 0; j < N_COLS; j++) {
83                 mCells[i][j].setText("");
84             }
85         }
86         getSystemService(AutofillManager.class).cancel();
87     }
88 
getCell(int row, int column)89     EditText getCell(int row, int column) {
90         return mCells[row - 1][column - 1];
91     }
92 
getResourceId(int line, int col)93     public static String getResourceId(int line, int col) {
94         return "l" + line + "c" + col;
95     }
96 
onCell(int row, int column, Visitor<EditText> v)97     public void onCell(int row, int column, Visitor<EditText> v) {
98         final EditText cell = getCell(row, column);
99         syncRunOnUiThread(() -> v.visit(cell));
100     }
101 
focusCell(int row, int column)102     public void focusCell(int row, int column) {
103         onCell(row, column, EditText::requestFocus);
104     }
105 
clearCell(int row, int column)106     public void clearCell(int row, int column) {
107         onCell(row, column, (c) -> c.setText(""));
108     }
109 
setText(int row, int column, String text)110     public void setText(int row, int column, String text) {
111         onCell(row, column, (c) -> c.setText(text));
112     }
113 
forceAutofill(int row, int column)114     public void forceAutofill(int row, int column) {
115         onCell(row, column, (c) -> getAutofillManager().requestAutofill(c));
116     }
117 
removeCell(int row, int column)118     public void removeCell(int row, int column) {
119         onCell(row, column, (c) -> mGrid.removeView(c));
120     }
121 
addCell(int row, int column, EditText cell)122     public void addCell(int row, int column, EditText cell) {
123         mCells[row - 1][column - 1] = cell;
124         // TODO: ideally it should be added in the right place...
125         syncRunOnUiThread(() -> mGrid.addView(cell));
126     }
127 
triggerAutofill(boolean manually, int row, int column)128     public void triggerAutofill(boolean manually, int row, int column) {
129         if (manually) {
130             forceAutofill(row, column);
131         } else {
132             focusCell(row, column);
133         }
134     }
135 
getText(int row, int column)136     public String getText(int row, int column) throws InterruptedException {
137         final BlockingQueue<String> queue = new LinkedBlockingQueue<>(1);
138         onCell(row, column, (c) -> queue.offer(c.getText().toString()));
139         final String text = queue.poll(100, TimeUnit.MILLISECONDS);
140         if (text == null) {
141             throw new RetryableException("text not set in 100ms");
142         }
143         return text;
144     }
145 
expectAutofill()146     public FillExpectation expectAutofill() {
147         return new FillExpectation();
148     }
149 
dumpCells()150     public void dumpCells() {
151         final StringBuilder output = new StringBuilder("dumpCells():\n");
152         for (int i = 0; i < N_ROWS; i++) {
153             for (int j = 0; j < N_COLS; j++) {
154                 final String id = getResourceId(i + 1, j + 1);
155                 final String value = mCells[i][j].getText().toString();
156                 output.append('\t').append(id).append("='").append(value).append("'\n");
157             }
158         }
159         Log.d(TAG, output.toString());
160     }
161 
162     final class FillExpectation {
163 
164         private final ArrayList<OneTimeTextWatcher> mWatchers = new ArrayList<>();
165 
onCell(int line, int col, String value)166         public FillExpectation onCell(int line, int col, String value) {
167             final String resourceId = getResourceId(line, col);
168             final EditText cell = getCell(line, col);
169             final OneTimeTextWatcher watcher = new OneTimeTextWatcher(resourceId, cell, value);
170             mWatchers.add(watcher);
171             cell.addTextChangedListener(watcher);
172             return this;
173         }
174 
assertAutoFilled()175         public void assertAutoFilled() throws Exception {
176             try {
177                 for (int i = 0; i < mWatchers.size(); i++) {
178                     final OneTimeTextWatcher watcher = mWatchers.get(i);
179                     watcher.assertAutoFilled();
180                 }
181             } catch (AssertionError | Exception e) {
182                 dumpCells();
183                 throw e;
184             }
185         }
186     }
187 }
188