*
Disclaimer: you should not store sensitive fields like user data unencrypted. * This is done here only for simplicity and learning purposes. */ public class SharedPrefsAutofillRepository implements AutofillDataSource { private static final String SHARED_PREF_KEY = "com.example.android.autofill" + ".service.datasource.AutofillDataSource"; private static final String CLIENT_FORM_DATA_KEY = "loginCredentialDatasets"; private static final String DATASET_NUMBER_KEY = "datasetNumber"; private static SharedPrefsAutofillRepository sInstance; private SharedPrefsAutofillRepository() { } public static SharedPrefsAutofillRepository getInstance() { if (sInstance == null) { sInstance = new SharedPrefsAutofillRepository(); } return sInstance; } @Override public HashMap getFilledAutofillFieldCollection( Context context, List focusedAutofillHints, List allAutofillHints) { boolean hasDataForFocusedAutofillHints = false; HashMap clientFormDataMap = new HashMap<>(); Set clientFormDataStringSet = getAllAutofillDataStringSet(context); for (String clientFormDataString : clientFormDataStringSet) { Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); FilledAutofillFieldCollection filledAutofillFieldCollection = gson.fromJson(clientFormDataString, FilledAutofillFieldCollection.class); if (filledAutofillFieldCollection != null) { if (filledAutofillFieldCollection.helpsWithHints(focusedAutofillHints)) { // Saved data has data relevant to at least 1 of the hints associated with the // View in focus. hasDataForFocusedAutofillHints = true; } if (filledAutofillFieldCollection.helpsWithHints(allAutofillHints)) { // Saved data has data relevant to at least 1 of these hints associated with any // of the Views in the hierarchy. clientFormDataMap.put(filledAutofillFieldCollection.getDatasetName(), filledAutofillFieldCollection); } } } if (hasDataForFocusedAutofillHints) { return clientFormDataMap; } else { return null; } } @Override public void saveFilledAutofillFieldCollection(Context context, FilledAutofillFieldCollection filledAutofillFieldCollection) { String datasetName = "dataset-" + getDatasetNumber(context); filledAutofillFieldCollection.setDatasetName(datasetName); Set allAutofillData = getAllAutofillDataStringSet(context); Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); allAutofillData.add(gson.toJson(filledAutofillFieldCollection)); saveAllAutofillDataStringSet(context, allAutofillData); incrementDatasetNumber(context); } @Override public void clear(Context context) { context.getApplicationContext() .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE) .edit() .remove(CLIENT_FORM_DATA_KEY) .remove(DATASET_NUMBER_KEY) .apply(); } private Set getAllAutofillDataStringSet(Context context) { return context.getApplicationContext() .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE) .getStringSet(CLIENT_FORM_DATA_KEY, new ArraySet()); } private void saveAllAutofillDataStringSet(Context context, Set allAutofillDataStringSet) { context.getApplicationContext() .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE) .edit() .putStringSet(CLIENT_FORM_DATA_KEY, allAutofillDataStringSet) .apply(); } /** * For simplicity, datasets will be named in the form "dataset-X" where X means * this was the Xth dataset saved. */ private int getDatasetNumber(Context context) { return context.getApplicationContext() .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE) .getInt(DATASET_NUMBER_KEY, 0); } /** * Every time a dataset is saved, this should be called to increment the dataset number. * (only important for this service's dataset naming scheme). */ private void incrementDatasetNumber(Context context) { context.getApplicationContext() .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE) .edit() .putInt(DATASET_NUMBER_KEY, getDatasetNumber(context) + 1) .apply(); } }