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 
17 package com.example.android.autofill.app.commoncases;
18 
19 import android.content.Intent;
20 import android.graphics.drawable.Drawable;
21 import android.os.Bundle;
22 import android.support.annotation.DrawableRes;
23 import android.support.annotation.NonNull;
24 import android.support.annotation.Nullable;
25 import android.support.annotation.RequiresApi;
26 import android.support.annotation.StringRes;
27 import android.support.v7.app.AppCompatActivity;
28 import android.support.v7.recyclerview.extensions.ListAdapter;
29 import android.support.v7.util.DiffUtil;
30 import android.support.v7.widget.DividerItemDecoration;
31 import android.support.v7.widget.LinearLayoutManager;
32 import android.support.v7.widget.RecyclerView;
33 import android.text.Editable;
34 import android.text.InputType;
35 import android.text.TextWatcher;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import android.view.autofill.AutofillId;
40 import android.view.autofill.AutofillManager;
41 import android.widget.EditText;
42 import android.widget.ImageView;
43 import android.widget.TextView;
44 
45 import com.example.android.autofill.app.R;
46 import com.example.android.autofill.app.WelcomeActivity;
47 
48 import java.util.Arrays;
49 import java.util.List;
50 
51 /**
52  * This is mostly a normal Activity containing a RecyclerView. The only difference is, the rows in
53  * the RecyclerView have autofillable fields. Therefore, when we bind data to a recycled view, we
54  * need to also set the {@link AutofillId} on the view.
55  */
56 @RequiresApi(28)
57 public class RecyclerViewActivity extends AppCompatActivity {
58     private AutofillManager mAfm;
59     private List<FieldMetadata> mFields;
60     private FieldAdapter mFieldAdapter;
61 
62     @Override
onCreate(@ullable Bundle savedInstanceState)63     protected void onCreate(@Nullable Bundle savedInstanceState) {
64         super.onCreate(savedInstanceState);
65         setContentView(R.layout.recycler_view_activity);
66         RecyclerView recyclerView = findViewById(R.id.recyclerView);
67         mAfm = getSystemService(AutofillManager.class);
68 
69         // Init RecyclerView
70         LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
71         DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(
72                 recyclerView.getContext(), layoutManager.getOrientation());
73         recyclerView.addItemDecoration(dividerItemDecoration);
74         mFields = initList();
75         mFieldAdapter = new FieldAdapter(mFields);
76         recyclerView.setAdapter(mFieldAdapter);
77         recyclerView.setLayoutManager(layoutManager);
78 
79         // Init submit and clear buttons.
80         findViewById(R.id.submitButton).setOnClickListener(new View.OnClickListener() {
81             @Override
82             public void onClick(View v) {
83                 submit();
84             }
85         });
86         findViewById(R.id.clearButton).setOnClickListener(new View.OnClickListener() {
87             @Override
88             public void onClick(View v) {
89                 AutofillManager afm = getSystemService(AutofillManager.class);
90                 if (afm != null) {
91                     afm.cancel();
92                 }
93                 resetFields();
94             }
95         });
96     }
97 
resetFields()98     private void resetFields() {
99         for (FieldMetadata fieldMetadata : mFields) {
100             fieldMetadata.setEnteredText("");
101         }
102         mFieldAdapter.notifyDataSetChanged();
103     }
104 
submit()105     private void submit() {
106         Intent intent = WelcomeActivity.getStartActivityIntent(RecyclerViewActivity.this);
107         startActivity(intent);
108         finish();
109     }
110 
initList()111     public List<FieldMetadata> initList() {
112         return Arrays.asList(
113                 new FieldMetadata(
114                         mAfm.getNextAutofillId(),
115                         View.AUTOFILL_HINT_NAME,
116                         R.string.recycler_view_label_name,
117                         R.drawable.ic_person_black_24dp,
118                         InputType.TYPE_CLASS_TEXT
119                 ),
120                 new FieldMetadata(
121                         mAfm.getNextAutofillId(),
122                         "bday-month",
123                         R.string.recycler_view_label_birthday_month,
124                         R.drawable.ic_person_black_24dp,
125                         InputType.TYPE_CLASS_NUMBER
126                 ),
127                 new FieldMetadata(
128                         mAfm.getNextAutofillId(),
129                         View.AUTOFILL_HINT_EMAIL_ADDRESS,
130                         R.string.recycler_view_label_email,
131                         R.drawable.ic_person_black_24dp,
132                         InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
133                 ),
134                 new FieldMetadata(
135                         mAfm.getNextAutofillId(),
136                         View.AUTOFILL_HINT_PHONE,
137                         R.string.recycler_view_label_phone,
138                         R.drawable.ic_person_black_24dp,
139                         InputType.TYPE_CLASS_PHONE
140                 ),
141                 new FieldMetadata(
142                         mAfm.getNextAutofillId(),
143                         "tel_extension",
144                         R.string.recycler_view_label_tel_extension,
145                         R.drawable.ic_person_black_24dp,
146                         InputType.TYPE_CLASS_PHONE
147                 ),
148                 new FieldMetadata(
149                         mAfm.getNextAutofillId(),
150                         View.AUTOFILL_HINT_CREDIT_CARD_NUMBER,
151                         R.string.recycler_view_label_cc_number,
152                         R.drawable.ic_person_black_24dp,
153                         InputType.TYPE_CLASS_NUMBER
154                 ),
155                 new FieldMetadata(
156                         mAfm.getNextAutofillId(),
157                         View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE,
158                         R.string.recycler_view_label_cc_sc,
159                         R.drawable.ic_person_black_24dp,
160                         InputType.TYPE_CLASS_NUMBER
161                 ),
162                 new FieldMetadata(
163                         mAfm.getNextAutofillId(),
164                         View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH,
165                         R.string.recycler_view_label_cc_exp_month,
166                         R.drawable.ic_person_black_24dp,
167                         InputType.TYPE_CLASS_NUMBER
168                 ),
169                 new FieldMetadata(
170                         mAfm.getNextAutofillId(),
171                         View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR,
172                         R.string.recycler_view_label_cc_exp_year,
173                         R.drawable.ic_person_black_24dp,
174                         InputType.TYPE_CLASS_NUMBER
175                 ),
176                 new FieldMetadata(
177                         mAfm.getNextAutofillId(),
178                         "address-line1",
179                         R.string.recycler_view_label_address_line_1,
180                         R.drawable.ic_person_black_24dp,
181                         InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS
182                 ),
183                 new FieldMetadata(
184                         mAfm.getNextAutofillId(),
185                         "address-line2",
186                         R.string.recycler_view_label_address_line_2,
187                         R.drawable.ic_person_black_24dp,
188                         InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS
189                 ),
190                 new FieldMetadata(
191                         mAfm.getNextAutofillId(),
192                         "address-line3",
193                         R.string.recycler_view_label_address_line_3,
194                         R.drawable.ic_person_black_24dp,
195                         InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS
196                 ),
197                 new FieldMetadata(
198                         mAfm.getNextAutofillId(),
199                         View.AUTOFILL_HINT_POSTAL_CODE,
200                         R.string.recycler_view_label_postal_code,
201                         R.drawable.ic_person_black_24dp,
202                         InputType.TYPE_CLASS_NUMBER
203                 ),
204                 new FieldMetadata(
205                         mAfm.getNextAutofillId(),
206                         "bday-year",
207                         R.string.recycler_view_label_birthday_year,
208                         R.drawable.ic_person_black_24dp,
209                         InputType.TYPE_CLASS_NUMBER
210                 )
211         );
212     }
213 
214     static class FieldAdapter extends ListAdapter<FieldMetadata, FieldViewHolder> {
215         private List<FieldMetadata> mFields;
FieldAdapter(List<FieldMetadata> fields)216         public FieldAdapter(List<FieldMetadata> fields) {
217             super(new FieldDiff());
218             mFields = fields;
219             submitList(mFields);
220         }
221 
222         @NonNull
223         @Override
onCreateViewHolder(@onNull ViewGroup parent, int viewType)224         public FieldViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
225             return new FieldViewHolder(LayoutInflater.from(parent.getContext())
226                     .inflate(R.layout.user_data_field, parent, false),
227                     new FieldWatcher(mFields));
228         }
229 
230         @Override
onBindViewHolder(@onNull FieldViewHolder holder, int position)231         public void onBindViewHolder(@NonNull FieldViewHolder holder, int position) {
232             holder.bind(getItem(position));
233         }
234     }
235 
236     static class FieldViewHolder extends RecyclerView.ViewHolder {
237         ImageView mIcon;
238         TextView mLabel;
239         EditText mField;
240         FieldWatcher mWatcher;
241 
FieldViewHolder(@onNull View itemView, FieldWatcher textWatcher)242         public FieldViewHolder(@NonNull View itemView, FieldWatcher textWatcher) {
243             super(itemView);
244             mIcon = itemView.findViewById(R.id.icon);
245             mLabel = itemView.findViewById(R.id.label);
246             mField = itemView.findViewById(R.id.field);
247             mWatcher = textWatcher;
248             mField.addTextChangedListener(mWatcher);
249         }
250 
bind(FieldMetadata fieldMetadata)251         void bind(FieldMetadata fieldMetadata) {
252             mWatcher.updatePosition(getAdapterPosition());
253             Drawable drawable = mIcon.getResources().getDrawable(fieldMetadata.getIconRes());
254             mIcon.setImageDrawable(drawable);
255             mLabel.setText(fieldMetadata.getLabelRes());
256             mField.setAutofillHints(fieldMetadata.getAutofillHint());
257             mField.setInputType(fieldMetadata.getInputType());
258             mField.setText(fieldMetadata.getEnteredText());
259 
260             // IMPORTANT: setAutofillId of recycled View.
261             mField.setAutofillId(fieldMetadata.getAutofillId());
262         }
263     }
264 
265     /**
266      * TextWatcher implementation to ensure EditTexts get recycled properly.
267      */
268     static class FieldWatcher implements TextWatcher {
269         private int mPosition;
270         private List<FieldMetadata> mFields;
271 
FieldWatcher(List<FieldMetadata> fields)272         public FieldWatcher(List<FieldMetadata> fields) {
273             mFields = fields;
274         }
275 
updatePosition(int position)276         public void updatePosition(int position) {
277             this.mPosition = position;
278         }
279 
280         @Override
beforeTextChanged(CharSequence charSequence, int i, int i1, int i2)281         public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
282             // NO-OP
283         }
284 
285         @Override
onTextChanged(CharSequence charSequence, int i, int i1, int i2)286         public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
287             mFields.get(mPosition).setEnteredText(charSequence);
288         }
289 
290         @Override
afterTextChanged(Editable editable)291         public void afterTextChanged(Editable editable) {
292             // NO-OP
293         }
294     }
295 
296     /**
297      * Model class that holds all of the data needed for a row in the {@link RecyclerView}.
298      */
299     static class FieldMetadata {
300         AutofillId mAutofillId;
301         String mAutofillHint;
302         @StringRes int mLabelRes;
303         @DrawableRes int mIconRes;
304         int mInputType;
305         CharSequence mEnteredText = "";
306 
FieldMetadata(AutofillId autofillId, String autofillHint, @StringRes int labelRes, @DrawableRes int iconRes, int inputType)307         FieldMetadata(AutofillId autofillId, String autofillHint, @StringRes int labelRes,
308                 @DrawableRes int iconRes, int inputType) {
309             mAutofillId = autofillId;
310             mAutofillHint = autofillHint;
311             mLabelRes = labelRes;
312             mIconRes = iconRes;
313             mInputType = inputType;
314         }
315 
getAutofillId()316         public AutofillId getAutofillId() {
317             return mAutofillId;
318         }
319 
getAutofillHint()320         public String getAutofillHint() {
321             return mAutofillHint;
322         }
323 
getLabelRes()324         public int getLabelRes() {
325             return mLabelRes;
326         }
327 
getIconRes()328         public int getIconRes() {
329             return mIconRes;
330         }
331 
getInputType()332         public int getInputType() {
333             return mInputType;
334         }
335 
setEnteredText(CharSequence enteredText)336         public void setEnteredText(CharSequence enteredText) {
337             mEnteredText = enteredText;
338         }
339 
getEnteredText()340         public CharSequence getEnteredText() {
341             return mEnteredText;
342         }
343     }
344 
345     static class FieldDiff extends DiffUtil.ItemCallback<FieldMetadata> {
346         @Override
areItemsTheSame(@onNull FieldMetadata oldItem, @NonNull FieldMetadata newItem)347         public boolean areItemsTheSame(@NonNull FieldMetadata oldItem,
348                 @NonNull FieldMetadata newItem) {
349             return oldItem == newItem;
350         }
351 
352         @Override
areContentsTheSame(@onNull FieldMetadata oldItem, @NonNull FieldMetadata newItem)353         public boolean areContentsTheSame(@NonNull FieldMetadata oldItem,
354                 @NonNull FieldMetadata newItem) {
355             return oldItem.equals(newItem);
356         }
357     }
358 }
359