public class ClientViewMetadata {
    private final List<String> mAllHints;
    private final int mSaveType;
    private final AutofillId[] mAutofillIds;
    private final String mWebDomain;
    private final AutofillId[] mFocusedIds;

    public ClientViewMetadata(List<String> allHints, int saveType, AutofillId[] autofillIds,
            AutofillId[] focusedIds, String webDomain) {
        mAllHints = allHints;
        mSaveType = saveType;
        mAutofillIds = autofillIds;
        mWebDomain = webDomain;
        mFocusedIds = focusedIds;
    }

    public List<String> getAllHints() {
        return mAllHints;
    }

    public AutofillId[] getAutofillIds() {
        return mAutofillIds;
    }

    public AutofillId[] getFocusedIds() {
        return mFocusedIds;
    }

    public int getSaveType() {
        return mSaveType;
    }

    public String getWebDomain() {
        return mWebDomain;
    }

    @Override public String toString() {
        return "ClientViewMetadata{" +
                "mAllHints=" + mAllHints +
                ", mSaveType=" + mSaveType +
                ", mAutofillIds=" + Arrays.toString(mAutofillIds) +
                ", mWebDomain='" + mWebDomain + '\'' +
                ", mFocusedIds=" + Arrays.toString(mFocusedIds) +
                '}';
    }
} public class ClientViewMetadataBuilder {
    private ClientParser mClientParser;
    private HashMap<String, FieldTypeWithHeuristics> mFieldTypesByAutofillHint;

    public ClientViewMetadataBuilder(ClientParser parser,
            HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint) {
        mClientParser = parser;
        mFieldTypesByAutofillHint = fieldTypesByAutofillHint;
    }

    public ClientViewMetadata buildClientViewMetadata() {
        List<String> allHints = new ArrayList<>();
        MutableInt saveType = new MutableInt(0);
        List<AutofillId> autofillIds = new ArrayList<>();
        StringBuilder webDomainBuilder = new StringBuilder();
        List<AutofillId> focusedAutofillIds = new ArrayList<>();
        mClientParser.parse((node) -> parseNode(node, allHints, saveType, autofillIds, focusedAutofillIds));
        mClientParser.parse((node) -> parseWebDomain(node, webDomainBuilder));
        String webDomain = webDomainBuilder.toString();
        AutofillId[] autofillIdsArray = autofillIds.toArray(new AutofillId[autofillIds.size()]);
        AutofillId[] focusedIds = focusedAutofillIds.toArray(new AutofillId[focusedAutofillIds.size()]);
        return new ClientViewMetadata(allHints, saveType.value, autofillIdsArray, focusedIds, webDomain);
    }

    private void parseWebDomain(AssistStructure.ViewNode viewNode, StringBuilder validWebDomain) {
        String webDomain = viewNode.getWebDomain();
        if (webDomain != null) {
            logd("child web domain: %s", webDomain);
            if (validWebDomain.length() > 0) {
                if (!webDomain.equals(validWebDomain.toString())) {
                    throw new SecurityException("Found multiple web domains: valid= "
                            + validWebDomain + ", child=" + webDomain);
                }
            } else {
                validWebDomain.append(webDomain);
            }
        }
    }

    private void parseNode(AssistStructure.ViewNode root, List<String> allHints,
            MutableInt autofillSaveType, List<AutofillId> autofillIds,
            List<AutofillId> focusedAutofillIds) {
        String[] hints = root.getAutofillHints();
        if (hints != null) {
            for (String hint : hints) {
                FieldTypeWithHeuristics fieldTypeWithHints = mFieldTypesByAutofillHint.get(hint);
                if (fieldTypeWithHints != null && fieldTypeWithHints.fieldType != null) {
                    allHints.add(hint);
                    autofillSaveType.value |= fieldTypeWithHints.fieldType.getSaveInfo();
                    autofillIds.add(root.getAutofillId());
                }
            }
        }
        if (root.isFocused()) {
            focusedAutofillIds.add(root.getAutofillId());
        }
    }
} public interface DataCallback<T> {
    void onLoaded(T object);

    void onDataNotAvailable(String msg, Object... params);
} public class DatasetAdapter {
    private final ClientParser mClientParser;

    public DatasetAdapter(ClientParser clientParser) {
        mClientParser = clientParser;
    }

    /**
     * Wraps autofill data in a {@link Dataset} object which can then be sent back to the client.
     */
    public Dataset buildDataset(HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint,
            DatasetWithFilledAutofillFields datasetWithFilledAutofillFields,
            RemoteViews remoteViews) {
        return buildDataset(fieldTypesByAutofillHint, datasetWithFilledAutofillFields, remoteViews,
                null);
    }

    public Dataset buildDatasetForFocusedNode(FilledAutofillField filledAutofillField,
            FieldType fieldType, RemoteViews remoteViews) {
        Dataset.Builder datasetBuilder = new Dataset.Builder(remoteViews);
        boolean setAtLeastOneValue = bindDatasetToFocusedNode(filledAutofillField,
                fieldType, datasetBuilder);
        if (!setAtLeastOneValue) {
            return null;
        }
        return datasetBuilder.build();
    }

    /**
     * Wraps autofill data in a {@link Dataset} object with an IntentSender, which can then be
     * sent back to the client.
     */
    public Dataset buildDataset(HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint,
            DatasetWithFilledAutofillFields datasetWithFilledAutofillFields,
            RemoteViews remoteViews, IntentSender intentSender) {
        Dataset.Builder datasetBuilder = new Dataset.Builder(remoteViews);
        if (intentSender != null) {
            datasetBuilder.setAuthentication(intentSender);
        }
        boolean setAtLeastOneValue = bindDataset(fieldTypesByAutofillHint,
                datasetWithFilledAutofillFields, datasetBuilder);
        if (!setAtLeastOneValue) {
            return null;
        }
        return datasetBuilder.build();
    }

    /**
     * Build an autofill {@link Dataset} using saved data and the client's AssistStructure.
     */
    private boolean bindDataset(HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint,
            DatasetWithFilledAutofillFields datasetWithFilledAutofillFields,
            Dataset.Builder datasetBuilder) {
        MutableBoolean setValueAtLeastOnce = new MutableBoolean(false);
        Map<String, FilledAutofillField> filledAutofillFieldsByTypeName =
                datasetWithFilledAutofillFields.filledAutofillFields.stream()
                        .collect(toMap(FilledAutofillField::getFieldTypeName, Function.identity()));
        mClientParser.parse((node) ->
                parseAutofillFields(node, fieldTypesByAutofillHint, filledAutofillFieldsByTypeName,
                        datasetBuilder, setValueAtLeastOnce)
        );
        return setValueAtLeastOnce.value;
    }

    private boolean bindDatasetToFocusedNode(FilledAutofillField field,
            FieldType fieldType, Dataset.Builder builder) {
        MutableBoolean setValueAtLeastOnce = new MutableBoolean(false);
        mClientParser.parse((node) -> {
            if (node.isFocused() && node.getAutofillId() != null) {
                bindValueToNode(node, field, builder, setValueAtLeastOnce);
            }
        });
        return setValueAtLeastOnce.value;
    }

    private void parseAutofillFields(AssistStructure.ViewNode viewNode,
            HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint,
            Map<String, FilledAutofillField> filledAutofillFieldsByTypeName,
            Dataset.Builder builder, MutableBoolean setValueAtLeastOnce) {
        String[] rawHints = viewNode.getAutofillHints();
        if (rawHints == null || rawHints.length == 0) {
            logv("No af hints at ViewNode - %s", viewNode.getIdEntry());
            return;
        }
        String fieldTypeName = AutofillHints.getFieldTypeNameFromAutofillHints(
                fieldTypesByAutofillHint, Arrays.asList(rawHints));
        if (fieldTypeName == null) {
            return;
        }
        FilledAutofillField field = filledAutofillFieldsByTypeName.get(fieldTypeName);
        if (field == null) {
            return;
        }
        bindValueToNode(viewNode, field, builder, setValueAtLeastOnce);
    }

    void bindValueToNode(AssistStructure.ViewNode viewNode,
            FilledAutofillField field, Dataset.Builder builder,
            MutableBoolean setValueAtLeastOnce) {
        AutofillId autofillId = viewNode.getAutofillId();
        if (autofillId == null) {
            logw("Autofill ID null for %s", viewNode.toString());
            return;
        }
        int autofillType = viewNode.getAutofillType();
        switch (autofillType) {
            case View.AUTOFILL_TYPE_LIST:
                CharSequence[] options = viewNode.getAutofillOptions();
                int listValue = -1;
                if (options != null) {
                    listValue = indexOf(viewNode.getAutofillOptions(), field.getTextValue());
                }
                if (listValue != -1) {
                    builder.setValue(autofillId, AutofillValue.forList(listValue));
                    setValueAtLeastOnce.value = true;
                }
                break;
            case View.AUTOFILL_TYPE_DATE:
                Long dateValue = field.getDateValue();
                if (dateValue != null) {
                    builder.setValue(autofillId, AutofillValue.forDate(dateValue));
                    setValueAtLeastOnce.value = true;
                }
                break;
            case View.AUTOFILL_TYPE_TEXT:
                String textValue = field.getTextValue();
                if (textValue != null) {
                    builder.setValue(autofillId, AutofillValue.forText(textValue));
                    setValueAtLeastOnce.value = true;
                }
                break;
            case View.AUTOFILL_TYPE_TOGGLE:
                Boolean toggleValue = field.getToggleValue();
                if (toggleValue != null) {
                    builder.setValue(autofillId, AutofillValue.forToggle(toggleValue));
                    setValueAtLeastOnce.value = true;
                }
                break;
            case View.AUTOFILL_TYPE_NONE:
            default:
                logw("Invalid autofill type - %d", autofillType);
                break;
        }
    }
} public class ResponseAdapter {
    private final Context mContext;
    private final DatasetAdapter mDatasetAdapter;
    private final String mPackageName;
    private final ClientViewMetadata mClientViewMetadata;

    public ResponseAdapter(Context context, ClientViewMetadata clientViewMetadata,
            String packageName, DatasetAdapter datasetAdapter) {
        mContext = context;
        mClientViewMetadata = clientViewMetadata;
        mDatasetAdapter = datasetAdapter;
        mPackageName = packageName;
    }

    public FillResponse buildResponseForFocusedNode(String datasetName, FilledAutofillField field,
            FieldType fieldType) {
        FillResponse.Builder responseBuilder = new FillResponse.Builder();
        RemoteViews remoteViews = RemoteViewsHelper.viewsWithNoAuth(
                mPackageName, datasetName);
        Dataset dataset = mDatasetAdapter.buildDatasetForFocusedNode(field, fieldType, remoteViews);
        if (dataset != null) {
            responseBuilder.addDataset(dataset);
            return responseBuilder.build();
        } else {
            return null;
        }
    }

    /**
     * Wraps autofill data in a Response object (essentially a series of Datasets) which can then
     * be sent back to the client View.
     */
    public FillResponse buildResponse(HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint,
            List<DatasetWithFilledAutofillFields> datasets, boolean datasetAuth) {
        FillResponse.Builder responseBuilder = new FillResponse.Builder();
        if (datasets != null) {
            for (DatasetWithFilledAutofillFields datasetWithFilledAutofillFields : datasets) {
                if (datasetWithFilledAutofillFields != null) {
                    Dataset dataset;
                    String datasetName = datasetWithFilledAutofillFields.autofillDataset
                            .getDatasetName();
                    if (datasetAuth) {
                        IntentSender intentSender = AuthActivity.getAuthIntentSenderForDataset(
                                mContext, datasetName);
                        RemoteViews remoteViews = RemoteViewsHelper.viewsWithAuth(
                                mPackageName, datasetName);
                        dataset = mDatasetAdapter.buildDataset(fieldTypesByAutofillHint,
                                datasetWithFilledAutofillFields, remoteViews, intentSender);
                    } else {
                        RemoteViews remoteViews = RemoteViewsHelper.viewsWithNoAuth(
                                mPackageName, datasetName);
                        dataset = mDatasetAdapter.buildDataset(fieldTypesByAutofillHint,
                                datasetWithFilledAutofillFields, remoteViews);
                    }
                    if (dataset != null) {
                        responseBuilder.addDataset(dataset);
                    }
                }
            }
        }
        int saveType = mClientViewMetadata.getSaveType();
        AutofillId[] autofillIds = mClientViewMetadata.getAutofillIds();
        if (autofillIds != null && autofillIds.length > 0) {
            SaveInfo saveInfo = new SaveInfo.Builder(saveType, autofillIds).build();
            responseBuilder.setSaveInfo(saveInfo);
            return responseBuilder.build();
        } else {
            return null;
        }
    }

    public FillResponse buildResponse(IntentSender sender, RemoteViews remoteViews) {
        FillResponse.Builder responseBuilder = new FillResponse.Builder();
        int saveType = mClientViewMetadata.getSaveType();
        AutofillId[] autofillIds = mClientViewMetadata.getAutofillIds();
        if (autofillIds != null && autofillIds.length > 0) {
            SaveInfo saveInfo = new SaveInfo.Builder(saveType, autofillIds).build();
            responseBuilder.setSaveInfo(saveInfo);
            responseBuilder.setAuthentication(autofillIds, sender, remoteViews);
            return responseBuilder.build();
        } else {
            return null;
        }
    }

    public FillResponse buildManualResponse(IntentSender sender, RemoteViews remoteViews) {
        FillResponse.Builder responseBuilder = new FillResponse.Builder();
        int saveType = mClientViewMetadata.getSaveType();
        AutofillId[] focusedIds = mClientViewMetadata.getFocusedIds();
        if (focusedIds != null && focusedIds.length > 0) {
            SaveInfo saveInfo = new SaveInfo.Builder(saveType, focusedIds).build();
            return responseBuilder.setSaveInfo(saveInfo)
                    .setAuthentication(focusedIds, sender, remoteViews)
                    .build();
        } else {
            return null;
        }
    }
} public interface DefaultFieldTypesSource {
    List<DefaultFieldTypeWithHints> getDefaultFieldTypes();
} public class DefaultFieldTypesLocalJsonSource implements DefaultFieldTypesSource {
    private static DefaultFieldTypesLocalJsonSource sInstance;

    private final Resources mResources;
    private final Gson mGson;

    private DefaultFieldTypesLocalJsonSource(Resources resources, Gson gson) {
        mResources = resources;
        mGson = gson;
    }

    public static DefaultFieldTypesLocalJsonSource getInstance(Resources resources, Gson gson) {
        if (sInstance == null) {
            sInstance = new DefaultFieldTypesLocalJsonSource(resources, gson);
        }
        return sInstance;
    }

    @Override
    public List<DefaultFieldTypeWithHints> getDefaultFieldTypes() {
        Type fieldTypeListType =  TypeToken.getParameterized(List.class,
                DefaultFieldTypeWithHints.class).getType();
        InputStream is = mResources.openRawResource(R.raw.default_field_types);
        List<DefaultFieldTypeWithHints> fieldTypes = null;
        try(Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
            fieldTypes = mGson.fromJson(reader, fieldTypeListType);
        } catch (IOException e) {
            loge(e, "Exception during deserialization of FieldTypes.");
        }
        return fieldTypes;
    }
} public class LocalAutofillDataSource implements AutofillDataSource {
    public static final String SHARED_PREF_KEY = "com.example.android.autofill"
            + ".service.datasource.LocalAutofillDataSource";
    private static final String DATASET_NUMBER_KEY = "datasetNumber";
    private static final Object sLock = new Object();

    private static LocalAutofillDataSource sInstance;

    private final AutofillDao mAutofillDao;
    private final SharedPreferences mSharedPreferences;
    private final AppExecutors mAppExecutors;

    private LocalAutofillDataSource(SharedPreferences sharedPreferences, AutofillDao autofillDao,
            AppExecutors appExecutors) {
        mSharedPreferences = sharedPreferences;
        mAutofillDao = autofillDao;
        mAppExecutors = appExecutors;
    }

    public static LocalAutofillDataSource getInstance(SharedPreferences sharedPreferences,
            AutofillDao autofillDao, AppExecutors appExecutors) {
        synchronized (sLock) {
            if (sInstance == null) {
                sInstance = new LocalAutofillDataSource(sharedPreferences, autofillDao,
                        appExecutors);
            }
            return sInstance;
        }
    }

    public static void clearInstance() {
        synchronized (sLock) {
            sInstance = null;
        }
    }

    @Override
    public void getAutofillDatasets(List<String> allAutofillHints,
            DataCallback<List<DatasetWithFilledAutofillFields>> datasetsCallback) {
        mAppExecutors.diskIO().execute(() -> {
            final List<String> typeNames = getFieldTypesForAutofillHints(allAutofillHints)
                    .stream()
                    .map(FieldTypeWithHeuristics::getFieldType)
                    .map(FieldType::getTypeName)
                    .collect(Collectors.toList());
            List<DatasetWithFilledAutofillFields> datasetsWithFilledAutofillFields =
                    mAutofillDao.getDatasets(typeNames);
            mAppExecutors.mainThread().execute(() ->
                    datasetsCallback.onLoaded(datasetsWithFilledAutofillFields)
            );
        });
    }

    @Override
    public void getAllAutofillDatasets(
            DataCallback<List<DatasetWithFilledAutofillFields>> datasetsCallback) {
        mAppExecutors.diskIO().execute(() -> {
            List<DatasetWithFilledAutofillFields> datasetsWithFilledAutofillFields =
                    mAutofillDao.getAllDatasets();
            mAppExecutors.mainThread().execute(() ->
                    datasetsCallback.onLoaded(datasetsWithFilledAutofillFields)
            );
        });
    }

    @Override
    public void getAutofillDataset(List<String> allAutofillHints, String datasetName,
            DataCallback<DatasetWithFilledAutofillFields> datasetsCallback) {
        mAppExecutors.diskIO().execute(() -> {
            // Room does not support TypeConverters for collections.
            List<DatasetWithFilledAutofillFields> autofillDatasetFields =
                    mAutofillDao.getDatasetsWithName(allAutofillHints, datasetName);
            if (autofillDatasetFields != null && !autofillDatasetFields.isEmpty()) {
                if (autofillDatasetFields.size() > 1) {
                    logw("More than 1 dataset with name %s", datasetName);
                }
                DatasetWithFilledAutofillFields dataset = autofillDatasetFields.get(0);

                mAppExecutors.mainThread().execute(() ->
                        datasetsCallback.onLoaded(dataset)
                );
            } else {
                mAppExecutors.mainThread().execute(() ->
                        datasetsCallback.onDataNotAvailable("No data found.")
                );
            }
        });
    }


    @Override
    public void saveAutofillDatasets(List<DatasetWithFilledAutofillFields>
            datasetsWithFilledAutofillFields) {
        mAppExecutors.diskIO().execute(() -> {
            for (DatasetWithFilledAutofillFields datasetWithFilledAutofillFields :
                    datasetsWithFilledAutofillFields) {
                List<FilledAutofillField> filledAutofillFields =
                        datasetWithFilledAutofillFields.filledAutofillFields;
                AutofillDataset autofillDataset = datasetWithFilledAutofillFields.autofillDataset;
                mAutofillDao.insertAutofillDataset(autofillDataset);
                mAutofillDao.insertFilledAutofillFields(filledAutofillFields);
            }
        });
        incrementDatasetNumber();
    }

    @Override
    public void saveResourceIdHeuristic(ResourceIdHeuristic resourceIdHeuristic) {
        mAppExecutors.diskIO().execute(() -> {
            mAutofillDao.insertResourceIdHeuristic(resourceIdHeuristic);
        });
    }

    @Override
    public void getFieldTypes(DataCallback<List<FieldTypeWithHeuristics>> fieldTypesCallback) {
        mAppExecutors.diskIO().execute(() -> {
            List<FieldTypeWithHeuristics> fieldTypeWithHints = mAutofillDao.getFieldTypesWithHints();
            mAppExecutors.mainThread().execute(() -> {
                if (fieldTypeWithHints != null) {
                    fieldTypesCallback.onLoaded(fieldTypeWithHints);
                } else {
                    fieldTypesCallback.onDataNotAvailable("Field Types not found.");
                }
            });
        });
    }

    @Override
    public void getFieldTypeByAutofillHints(
            DataCallback<HashMap<String, FieldTypeWithHeuristics>> fieldTypeMapCallback) {
        mAppExecutors.diskIO().execute(() -> {
            HashMap<String, FieldTypeWithHeuristics> hintMap = getFieldTypeByAutofillHints();
            mAppExecutors.mainThread().execute(() -> {
                if (hintMap != null) {
                    fieldTypeMapCallback.onLoaded(hintMap);
                } else {
                    fieldTypeMapCallback.onDataNotAvailable("FieldTypes not found");
                }
            });
        });
    }

    @Override
    public void getFilledAutofillField(String datasetId, String fieldTypeName, DataCallback<FilledAutofillField> fieldCallback) {
        mAppExecutors.diskIO().execute(() -> {
            FilledAutofillField filledAutofillField = mAutofillDao.getFilledAutofillField(datasetId, fieldTypeName);
            mAppExecutors.mainThread().execute(() -> {
                fieldCallback.onLoaded(filledAutofillField);
            });
        });
    }

    @Override
    public void getFieldType(String fieldTypeName, DataCallback<FieldType> fieldTypeCallback) {
        mAppExecutors.diskIO().execute(() -> {
            FieldType fieldType = mAutofillDao.getFieldType(fieldTypeName);
            mAppExecutors.mainThread().execute(() -> {
                fieldTypeCallback.onLoaded(fieldType);
            });
        });
    }

    public void getAutofillDatasetWithId(String datasetId,
            DataCallback<DatasetWithFilledAutofillFields> callback) {
        mAppExecutors.diskIO().execute(() -> {
            DatasetWithFilledAutofillFields dataset =
                    mAutofillDao.getAutofillDatasetWithId(datasetId);
            mAppExecutors.mainThread().execute(() -> {
                callback.onLoaded(dataset);
            });
        });
    }

    private HashMap<String, FieldTypeWithHeuristics> getFieldTypeByAutofillHints() {
        HashMap<String, FieldTypeWithHeuristics> hintMap = new HashMap<>();
        List<FieldTypeWithHeuristics> fieldTypeWithHints =
                mAutofillDao.getFieldTypesWithHints();
        if (fieldTypeWithHints != null) {
            for (FieldTypeWithHeuristics fieldType : fieldTypeWithHints) {
                for (AutofillHint hint : fieldType.autofillHints) {
                    hintMap.put(hint.mAutofillHint, fieldType);
                }
            }
            return hintMap;
        } else {
            return null;
        }
    }

    private List<FieldTypeWithHeuristics> getFieldTypesForAutofillHints(List<String> autofillHints) {
        return mAutofillDao.getFieldTypesForAutofillHints(autofillHints);
    }

    @Override
    public void clear() {
        mAppExecutors.diskIO().execute(() -> {
            mAutofillDao.clearAll();
            mSharedPreferences.edit().putInt(DATASET_NUMBER_KEY, 0).apply();
        });
    }

    /**
     * For simplicity, {@link Dataset}s will be named in the form {@code dataset-X.P} where
     * {@code X} means this was the Xth group of datasets saved, and {@code P} refers to the dataset
     * partition number. This method returns the appropriate {@code X}.
     */
    public int getDatasetNumber() {
        return mSharedPreferences.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() {
        mSharedPreferences.edit().putInt(DATASET_NUMBER_KEY, getDatasetNumber() + 1).apply();
    }
} @Dao
public interface AutofillDao {
    /**
     * Fetches a list of datasets associated to autofill fields on the page.
     *
     * @param allAutofillHints Filtering parameter; represents all of the hints associated with
     *                         all of the views on the page.
     */
    @Query("SELECT DISTINCT id, datasetName FROM FilledAutofillField, AutofillDataset" +
            " WHERE AutofillDataset.id = FilledAutofillField.datasetId" +
            " AND FilledAutofillField.fieldTypeName IN (:allAutofillHints)")
    List<DatasetWithFilledAutofillFields> getDatasets(List<String> allAutofillHints);

    @Query("SELECT DISTINCT id, datasetName FROM FilledAutofillField, AutofillDataset" +
            " WHERE AutofillDataset.id = FilledAutofillField.datasetId")
    List<DatasetWithFilledAutofillFields> getAllDatasets();

    /**
     * Fetches a list of datasets associated to autofill fields. It should only return a dataset
     * if that dataset has an autofill field associate with the view the user is focused on, and
     * if that dataset's name matches the name passed in.
     *
     * @param fieldTypes Filtering parameter; represents all of the field types associated with
     *                         all of the views on the page.
     * @param datasetName      Filtering parameter; only return datasets with this name.
     */
    @Query("SELECT DISTINCT id, datasetName FROM FilledAutofillField, AutofillDataset" +
            " WHERE AutofillDataset.id = FilledAutofillField.datasetId" +
            " AND AutofillDataset.datasetName = (:datasetName)" +
            " AND FilledAutofillField.fieldTypeName IN (:fieldTypes)")
    List<DatasetWithFilledAutofillFields> getDatasetsWithName(
            List<String> fieldTypes, String datasetName);

    @Query("SELECT DISTINCT typeName, autofillTypes, saveInfo, partition, strictExampleSet, " +
            "textTemplate, dateTemplate" +
            " FROM FieldType, AutofillHint" +
            " WHERE FieldType.typeName = AutofillHint.fieldTypeName" +
            " UNION " +
            "SELECT DISTINCT typeName, autofillTypes, saveInfo, partition, strictExampleSet, " +
            "textTemplate, dateTemplate" +
            " FROM FieldType, ResourceIdHeuristic" +
            " WHERE FieldType.typeName = ResourceIdHeuristic.fieldTypeName")
    List<FieldTypeWithHeuristics> getFieldTypesWithHints();

    @Query("SELECT DISTINCT typeName, autofillTypes, saveInfo, partition, strictExampleSet, " +
            "textTemplate, dateTemplate" +
            " FROM FieldType, AutofillHint" +
            " WHERE FieldType.typeName = AutofillHint.fieldTypeName" +
            " AND AutofillHint.autofillHint IN (:autofillHints)" +
            " UNION " +
            "SELECT DISTINCT typeName, autofillTypes, saveInfo, partition, strictExampleSet, " +
            "textTemplate, dateTemplate" +
            " FROM FieldType, ResourceIdHeuristic" +
            " WHERE FieldType.typeName = ResourceIdHeuristic.fieldTypeName")
    List<FieldTypeWithHeuristics> getFieldTypesForAutofillHints(List<String> autofillHints);

    @Query("SELECT DISTINCT id, datasetName FROM FilledAutofillField, AutofillDataset" +
            " WHERE AutofillDataset.id = FilledAutofillField.datasetId" +
            " AND AutofillDataset.id = (:datasetId)")
    DatasetWithFilledAutofillFields getAutofillDatasetWithId(String datasetId);

    @Query("SELECT * FROM FilledAutofillField" +
            " WHERE FilledAutofillField.datasetId = (:datasetId)" +
            " AND FilledAutofillField.fieldTypeName = (:fieldTypeName)")
    FilledAutofillField getFilledAutofillField(String datasetId, String fieldTypeName);

    @Query("SELECT * FROM FieldType" +
            " WHERE FieldType.typeName = (:fieldTypeName)")
    FieldType getFieldType(String fieldTypeName);

    /**
     * @param autofillFields Collection of autofill fields to be saved to the db.
     */
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertFilledAutofillFields(Collection<FilledAutofillField> autofillFields);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertAutofillDataset(AutofillDataset datasets);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertAutofillHints(List<AutofillHint> autofillHints);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertResourceIdHeuristic(ResourceIdHeuristic resourceIdHeuristic);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertFieldTypes(List<FieldType> fieldTypes);


    @Query("DELETE FROM AutofillDataset")
    void clearAll();
} @Database(entities = {
        FilledAutofillField.class,
        AutofillDataset.class,
        FieldType.class,
        AutofillHint.class,
        ResourceIdHeuristic.class
}, version = 1)
@TypeConverters({Converters.class})
public abstract class AutofillDatabase extends RoomDatabase {

    private static final Object sLock = new Object();
    private static AutofillDatabase sInstance;

    public static AutofillDatabase getInstance(Context context,
            DefaultFieldTypesSource defaultFieldTypesSource,
            AppExecutors appExecutors) {
        if (sInstance == null) {
            synchronized (sLock) {
                if (sInstance == null) {
                    sInstance = Room.databaseBuilder(context.getApplicationContext(),
                            AutofillDatabase.class, "AutofillSample.db")
                            .addCallback(new RoomDatabase.Callback() {
                                @Override
                                public void onCreate(@NonNull SupportSQLiteDatabase db) {
                                    appExecutors.diskIO().execute(() -> {
                                        List<DefaultFieldTypeWithHints> fieldTypes =
                                                defaultFieldTypesSource.getDefaultFieldTypes();
                                        AutofillDatabase autofillDatabase =
                                                getInstance(context, defaultFieldTypesSource,
                                                        appExecutors);
                                        autofillDatabase.saveDefaultFieldTypes(fieldTypes);
                                    });
                                }

                                @Override
                                public void onOpen(@NonNull SupportSQLiteDatabase db) {
                                    super.onOpen(db);
                                }
                            })
                            .build();
                }
            }
        }
        return sInstance;
    }

    private void saveDefaultFieldTypes(List<DefaultFieldTypeWithHints> defaultFieldTypes) {
        List<FieldType> storedFieldTypes = new ArrayList<>();
        List<AutofillHint> storedAutofillHints = new ArrayList<>();
        for (DefaultFieldTypeWithHints defaultType : defaultFieldTypes) {
            DefaultFieldTypeWithHints.DefaultFieldType defaultFieldType = defaultType.fieldType;
            List<String> autofillHints = defaultType.autofillHints;
            IntList autofillTypes = new IntList(defaultFieldType.autofillTypes);
            DefaultFieldTypeWithHints.DefaultFakeData defaultFakeData = defaultType.fieldType.fakeData;
            FakeData fakeData = new FakeData(new Converters.StringList(
                    defaultFakeData.strictExampleSet), defaultFakeData.textTemplate,
                    defaultFakeData.dateTemplate);
            FieldType storedFieldType = new FieldType(defaultFieldType.typeName, autofillTypes,
                    defaultFieldType.saveInfo, defaultFieldType.partition, fakeData);
            storedFieldTypes.add(storedFieldType);
            storedAutofillHints.addAll(autofillHints.stream()
                    .map((autofillHint) -> new AutofillHint(autofillHint,
                            storedFieldType.getTypeName())).collect(toList()));
        }
        AutofillDao autofillDao = autofillDao();
        autofillDao.insertFieldTypes(storedFieldTypes);
        autofillDao.insertAutofillHints(storedAutofillHints);
    }

    public abstract AutofillDao autofillDao();
} @Entity(primaryKeys = {"id"})
public class AutofillDataset {
    @NonNull
    @ColumnInfo(name = "id")
    private final String mId;

    @NonNull
    @ColumnInfo(name = "datasetName")
    private final String mDatasetName;

    @NonNull
    @ColumnInfo(name = "packageName")
    private final String mPackageName;

    public AutofillDataset(@NonNull String id, @NonNull String datasetName,
                           @NonNull String packageName) {
        mId = id;
        mDatasetName = datasetName;
        mPackageName = packageName;
    }

    @NonNull
    public String getId() {
        return mId;
    }

    @NonNull
    public String getDatasetName() {
        return mDatasetName;
    }

    @NonNull
    public String getPackageName() {
        return mPackageName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        AutofillDataset that = (AutofillDataset) o;

        if (!mId.equals(that.mId)) return false;
        if (!mDatasetName.equals(that.mDatasetName)) return false;
        return mPackageName.equals(that.mPackageName);
    }

    @Override
    public int hashCode() {
        int result = mId.hashCode();
        result = 31 * result + mDatasetName.hashCode();
        result = 31 * result + mPackageName.hashCode();
        return result;
    }
} public class DatasetWithFilledAutofillFields {
    @Embedded
    public AutofillDataset autofillDataset;

    @Relation(parentColumn = "id", entityColumn = "datasetId", entity = FilledAutofillField.class)
    public List<FilledAutofillField> filledAutofillFields;

    public void add(FilledAutofillField filledAutofillField) {
        if (filledAutofillFields == null) {
            this.filledAutofillFields = new ArrayList<>();
        }
        this.filledAutofillFields.add(filledAutofillField);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        DatasetWithFilledAutofillFields that = (DatasetWithFilledAutofillFields) o;

        if (autofillDataset != null ? !autofillDataset.equals(that.autofillDataset) :
                that.autofillDataset != null)
            return false;
        return filledAutofillFields != null ?
                filledAutofillFields.equals(that.filledAutofillFields) :
                that.filledAutofillFields == null;
    }

    @Override
    public int hashCode() {
        int result = autofillDataset != null ? autofillDataset.hashCode() : 0;
        result = 31 * result + (filledAutofillFields != null ? filledAutofillFields.hashCode() : 0);
        return result;
    }
} @Entity(primaryKeys = {"typeName"})
public class FieldType {
    @NonNull
    @ColumnInfo(name = "typeName")
    private final String mTypeName;

    @NonNull
    @ColumnInfo(name = "autofillTypes")
    private final IntList mAutofillTypes;

    @NonNull
    @ColumnInfo(name = "saveInfo")
    private final Integer mSaveInfo;

    @NonNull
    @ColumnInfo(name = "partition")
    private final Integer mPartition;

    @NonNull
    @Embedded
    private final FakeData mFakeData;

    public FieldType(@NonNull String typeName, @NonNull IntList autofillTypes,
            @NonNull Integer saveInfo, @NonNull Integer partition, @NonNull FakeData fakeData) {
        mTypeName = typeName;
        mAutofillTypes = autofillTypes;
        mSaveInfo = saveInfo;
        mPartition = partition;
        mFakeData = fakeData;
    }

    @NonNull
    public String getTypeName() {
        return mTypeName;
    }

    @NonNull
    public IntList getAutofillTypes() {
        return mAutofillTypes;
    }

    @NonNull
    public Integer getSaveInfo() {
        return mSaveInfo;
    }

    @NonNull
    public Integer getPartition() {
        return mPartition;
    }

    @NonNull
    public FakeData getFakeData() {
        return mFakeData;
    }
} public class FieldTypeWithHeuristics {
    @Embedded
    public FieldType fieldType;

    @Relation(parentColumn = "typeName", entityColumn = "fieldTypeName", entity = AutofillHint.class)
    public List<AutofillHint> autofillHints;

    @Relation(parentColumn = "typeName", entityColumn = "fieldTypeName", entity = ResourceIdHeuristic.class)
    public List<ResourceIdHeuristic> resourceIdHeuristics;

    public FieldType getFieldType() {
        return fieldType;
    }

    public List<AutofillHint> getAutofillHints() {
        return autofillHints;
    }

    public List<ResourceIdHeuristic> getResourceIdHeuristics() {
        return resourceIdHeuristics;
    }
} @Entity(primaryKeys = {"datasetId", "fieldTypeName"}, foreignKeys = {
        @ForeignKey(entity = AutofillDataset.class, parentColumns = "id",
                childColumns = "datasetId", onDelete = ForeignKey.CASCADE),
        @ForeignKey(entity = FieldType.class, parentColumns = "typeName",
                childColumns = "fieldTypeName", onDelete = ForeignKey.CASCADE)
})
public class FilledAutofillField {

    @NonNull
    @ColumnInfo(name = "datasetId")
    private final String mDatasetId;

    @Nullable
    @ColumnInfo(name = "textValue")
    private final String mTextValue;

    @Nullable
    @ColumnInfo(name = "dateValue")
    private final Long mDateValue;

    @Nullable
    @ColumnInfo(name = "toggleValue")
    private final Boolean mToggleValue;

    @NonNull
    @ColumnInfo(name = "fieldTypeName")
    private final String mFieldTypeName;

    public FilledAutofillField(@NonNull String datasetId, @NonNull String fieldTypeName,
                               @Nullable String textValue, @Nullable Long dateValue,
                               @Nullable Boolean toggleValue) {
        mDatasetId = datasetId;
        mFieldTypeName = fieldTypeName;
        mTextValue = textValue;
        mDateValue = dateValue;
        mToggleValue = toggleValue;
    }

    @Ignore
    public FilledAutofillField(@NonNull String datasetId,
            @NonNull String fieldTypeName, @Nullable String textValue, @Nullable Long dateValue) {
        this(datasetId, fieldTypeName, textValue, dateValue, null);
    }

    @Ignore
    public FilledAutofillField(@NonNull String datasetId, @NonNull String fieldTypeName,
                               @Nullable String textValue) {
        this(datasetId, fieldTypeName, textValue, null, null);
    }

    @Ignore
    public FilledAutofillField(@NonNull String datasetId, @NonNull String fieldTypeName,
                               @Nullable Long dateValue) {
        this(datasetId, fieldTypeName, null, dateValue, null);
    }

    @Ignore
    public FilledAutofillField(@NonNull String datasetId, @NonNull String fieldTypeName,
                               @Nullable Boolean toggleValue) {
        this(datasetId, fieldTypeName, null, null, toggleValue);
    }

    @Ignore
    public FilledAutofillField(@NonNull String datasetId, @NonNull String fieldTypeName) {
        this(datasetId, fieldTypeName, null, null, null);
    }

    @NonNull
    public String getDatasetId() {
        return mDatasetId;
    }

    @Nullable
    public String getTextValue() {
        return mTextValue;
    }

    @Nullable
    public Long getDateValue() {
        return mDateValue;
    }

    @Nullable
    public Boolean getToggleValue() {
        return mToggleValue;
    }

    @NonNull
    public String getFieldTypeName() {
        return mFieldTypeName;
    }

    public boolean isNull() {
        return mTextValue == null && mDateValue == null && mToggleValue == null;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        FilledAutofillField that = (FilledAutofillField) o;

        if (mTextValue != null ? !mTextValue.equals(that.mTextValue) : that.mTextValue != null)
            return false;
        if (mDateValue != null ? !mDateValue.equals(that.mDateValue) : that.mDateValue != null)
            return false;
        if (mToggleValue != null ? !mToggleValue.equals(that.mToggleValue) : that.mToggleValue != null)
            return false;
        return mFieldTypeName.equals(that.mFieldTypeName);
    }

    @Override
    public int hashCode() {
        int result = mTextValue != null ? mTextValue.hashCode() : 0;
        result = 31 * result + (mDateValue != null ? mDateValue.hashCode() : 0);
        result = 31 * result + (mToggleValue != null ? mToggleValue.hashCode() : 0);
        result = 31 * result + mFieldTypeName.hashCode();
        return result;
    }
} public class MyPreferences {
    private static final String RESPONSE_AUTH_KEY = "response_auth";
    private static final String DATASET_AUTH_KEY = "dataset_auth";
    private static final String MASTER_PASSWORD_KEY = "master_password";
    private static final String LOGGING_LEVEL = "logging_level";
    private static final String DAL_CHECK_REQUIRED = "dal_check_required";
    private static final String NUMBER_DATASETS = "number_datasets";
    private static MyPreferences sInstance;
    private final SharedPreferences mPrefs;

    private MyPreferences(Context context) {
        mPrefs = context.getApplicationContext().getSharedPreferences("my-settings",
                Context.MODE_PRIVATE);
    }

    public static MyPreferences getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new MyPreferences(context);
        }
        return sInstance;
    }

    /**
     * Gets whether {@link FillResponse}s should require authentication.
     */
    public boolean isResponseAuth() {
        return mPrefs.getBoolean(RESPONSE_AUTH_KEY, false);
    }

    /**
     * Enables/disables authentication for the entire autofill {@link FillResponse}.
     */
    public void setResponseAuth(boolean responseAuth) {
        mPrefs.edit().putBoolean(RESPONSE_AUTH_KEY, responseAuth).apply();
    }

    /**
     * Gets whether {@link Dataset}s should require authentication.
     */
    public boolean isDatasetAuth() {
        return mPrefs.getBoolean(DATASET_AUTH_KEY, false);
    }

    /**
     * Enables/disables authentication for individual autofill {@link Dataset}s.
     */
    public void setDatasetAuth(boolean datasetAuth) {
        mPrefs.edit().putBoolean(DATASET_AUTH_KEY, datasetAuth).apply();
    }

    /**
     * Gets autofill master username.
     */
    public String getMasterPassword() {
        return mPrefs.getString(MASTER_PASSWORD_KEY, null);
    }

    /**
     * Sets autofill master password.
     */
    public void setMasterPassword(@NonNull String masterPassword) {
        mPrefs.edit().putString(MASTER_PASSWORD_KEY, masterPassword).apply();
    }

    public void clearCredentials() {
        mPrefs.edit().remove(MASTER_PASSWORD_KEY).apply();
    }

    public Util.LogLevel getLoggingLevel() {
        return Util.LogLevel.values()[mPrefs.getInt(LOGGING_LEVEL, Util.LogLevel.Off.ordinal())];
    }

    public void setLoggingLevel(Util.LogLevel level) {
        mPrefs.edit().putInt(LOGGING_LEVEL, level.ordinal()).apply();
        Util.setLoggingLevel(level);
    }

    public Util.DalCheckRequirement getDalCheckRequirement() {
        return Util.DalCheckRequirement.values()[mPrefs.getInt(DAL_CHECK_REQUIRED,
                Util.DalCheckRequirement.AllUrls.ordinal())];
    }

    public void setDalCheckRequired(Util.DalCheckRequirement level) {
        mPrefs.edit().putInt(DAL_CHECK_REQUIRED, level.ordinal()).apply();
    }

    public int getNumberDatasets(int defaultNumber) {
        return mPrefs.getInt(NUMBER_DATASETS, defaultNumber);
    }

    public void setNumberDatasets(int number) {
        mPrefs.edit().putInt(NUMBER_DATASETS, number).apply();
    }
} public class AppExecutors {

    private static final int THREAD_COUNT = 3;

    private final Executor diskIO;

    private final Executor networkIO;

    private final Executor mainThread;

    @VisibleForTesting
    AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread) {
        this.diskIO = diskIO;
        this.networkIO = networkIO;
        this.mainThread = mainThread;
    }

    public AppExecutors() {
        this(new DiskIOThreadExecutor(), Executors.newFixedThreadPool(THREAD_COUNT),
                new MainThreadExecutor());
    }

    public Executor diskIO() {
        return diskIO;
    }

    public Executor networkIO() {
        return networkIO;
    }

    public Executor mainThread() {
        return mainThread;
    }

    private static class MainThreadExecutor implements Executor {
        private Handler mainThreadHandler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(@NonNull Runnable command) {
            mainThreadHandler.post(command);
        }
    }

    /**
     * Executor that runs a task on a new background thread.
     */
    private static class DiskIOThreadExecutor implements Executor {

        private final Executor mDiskIO;

        public DiskIOThreadExecutor() {
            mDiskIO = Executors.newSingleThreadExecutor();
        }

        @Override
        public void execute(@NonNull Runnable command) {
            mDiskIO.execute(command);
        }
    }
} public static void logd(String message, Object... params) {
    if (logDebugEnabled()) {
        Log.d(TAG, String.format(message, params));
    }
}
import android.app.Activity;
import android.app.PendingIntent;
import android.app.assist.AssistStructure;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.example.android.autofill.service.data.ClientViewMetadata;
import com.example.android.autofill.service.data.ClientViewMetadataBuilder;
import com.example.android.autofill.service.data.DataCallback;
import com.example.android.autofill.service.data.adapter.DatasetAdapter;
import com.example.android.autofill.service.data.adapter.ResponseAdapter;
import com.example.android.autofill.service.data.source.DefaultFieldTypesSource;
import com.example.android.autofill.service.data.source.local.DefaultFieldTypesLocalJsonSource;
import com.example.android.autofill.service.data.source.local.LocalAutofillDataSource;
import com.example.android.autofill.service.data.source.local.dao.AutofillDao;
import com.example.android.autofill.service.data.source.local.db.AutofillDatabase;
import com.example.android.autofill.service.model.AutofillDataset;
import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
import com.example.android.autofill.service.model.FieldType;
import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
import com.example.android.autofill.service.model.FilledAutofillField;
import com.example.android.autofill.service.settings.MyPreferences;
import com.example.android.autofill.service.util.AppExecutors;
import com.google.common.collect.ImmutableList;
import com.google.gson.GsonBuilder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
import static android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE;
import static android.view.autofill.AutofillManager.EXTRA_AUTHENTICATION_RESULT;
import static com.example.android.autofill.service.util.Util.logd;
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.autofill.service;





/**
 * When the user long-presses on an autofillable field and selects "Autofill", this activity is
 * launched to allow the user to select the dataset.
 */
public class ManualActivity extends AppCompatActivity {

    private static final int RC_SELECT_FIELD = 1;

    // Unique id for dataset intents.
    private static int sDatasetPendingIntentId = 0;

    private LocalAutofillDataSource mLocalAutofillDataSource;
    private DatasetAdapter mDatasetAdapter;
    private ResponseAdapter mResponseAdapter;
    private ClientViewMetadata mClientViewMetadata;
    private String mPackageName;
    private Intent mReplyIntent;
    private MyPreferences mPreferences;
    private List<DatasetWithFilledAutofillFields> mAllDatasets;
    private RecyclerView mRecyclerView;

    public static IntentSender getManualIntentSenderForResponse(Context context) {
        final Intent intent = new Intent(context, ManualActivity.class);
        return PendingIntent.getActivity(context, 0, intent,
                PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.multidataset_service_manual_activity);
        SharedPreferences sharedPreferences =
                getSharedPreferences(LocalAutofillDataSource.SHARED_PREF_KEY, Context.MODE_PRIVATE);
        DefaultFieldTypesSource defaultFieldTypesSource =
                DefaultFieldTypesLocalJsonSource.getInstance(getResources(),
                        new GsonBuilder().create());
        AutofillDao autofillDao = AutofillDatabase.getInstance(this,
                defaultFieldTypesSource, new AppExecutors()).autofillDao();
        mLocalAutofillDataSource = LocalAutofillDataSource.getInstance(sharedPreferences,
                autofillDao, new AppExecutors());
        mPackageName = getPackageName();
        mPreferences = MyPreferences.getInstance(this);
        mRecyclerView = findViewById(R.id.suggestionsList);
        mRecyclerView.addItemDecoration(new DividerItemDecoration(this, VERTICAL));
        mLocalAutofillDataSource.getAllAutofillDatasets(
                new DataCallback<List<DatasetWithFilledAutofillFields>>() {
                    @Override
                    public void onLoaded(List<DatasetWithFilledAutofillFields> datasets) {
                        mAllDatasets = datasets;
                        buildAdapter();
                    }

                    @Override
                    public void onDataNotAvailable(String msg, Object... params) {

                    }
                });
    }

    private void buildAdapter() {
        List<String> datasetIds = new ArrayList<>();
        List<String> datasetNames = new ArrayList<>();
        List<List<String>> allFieldTypes = new ArrayList<>();
        for (DatasetWithFilledAutofillFields dataset : mAllDatasets) {
            String datasetName = dataset.autofillDataset.getDatasetName();
            String datasetId = dataset.autofillDataset.getId();
            List<String> fieldTypes = new ArrayList<>();
            for (FilledAutofillField filledAutofillField : dataset.filledAutofillFields) {