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