public interface AutofillDataBuilder {
    List<DatasetWithFilledAutofillFields> buildDatasetsByPartition(int datasetNumber);
} public class ClientAutofillDataBuilder implements AutofillDataBuilder {
    private final ClientParser mClientParser;
    private final HashMap<String, FieldTypeWithHeuristics> mFieldTypesByAutofillHint;
    private final String mPackageName;

    public ClientAutofillDataBuilder(HashMap<String, FieldTypeWithHeuristics> fieldTypesByAutofillHint,
            String packageName, ClientParser clientParser) {
        mClientParser = clientParser;
        mFieldTypesByAutofillHint = fieldTypesByAutofillHint;
        mPackageName = packageName;
    }

    @Override
    public List<DatasetWithFilledAutofillFields> buildDatasetsByPartition(int datasetNumber) {
        ImmutableList.Builder<DatasetWithFilledAutofillFields> listBuilder =
                new ImmutableList.Builder<>();
        for (int partition : AutofillHints.PARTITIONS) {
            AutofillDataset autofillDataset = new AutofillDataset(UUID.randomUUID().toString(),
                    "dataset-" + datasetNumber + "." + partition, mPackageName);
            DatasetWithFilledAutofillFields dataset =
                    buildDatasetForPartition(autofillDataset, partition);
            if (dataset != null && dataset.filledAutofillFields != null) {
                listBuilder.add(dataset);
            }
        }
        return listBuilder.build();
    }

    /**
     * Parses a client view structure and build a dataset (in the form of a
     * {@link DatasetWithFilledAutofillFields}) from the view metadata found.
     */
    private DatasetWithFilledAutofillFields buildDatasetForPartition(AutofillDataset dataset,
            int partition) {
        DatasetWithFilledAutofillFields datasetWithFilledAutofillFields =
                new DatasetWithFilledAutofillFields();
        datasetWithFilledAutofillFields.autofillDataset = dataset;
        mClientParser.parse((node) ->
                parseAutofillFields(node, datasetWithFilledAutofillFields, partition)
        );
        return datasetWithFilledAutofillFields;

    }

    private void parseAutofillFields(AssistStructure.ViewNode viewNode,
            DatasetWithFilledAutofillFields datasetWithFilledAutofillFields, int partition) {
        String[] hints = viewNode.getAutofillHints();
        if (hints == null || hints.length == 0) {
            return;
        }
        AutofillValue autofillValue = viewNode.getAutofillValue();
        String textValue = null;
        Long dateValue = null;
        Boolean toggleValue = null;
        CharSequence[] autofillOptions = null;
        Integer listIndex = null;
        if (autofillValue != null) {
            if (autofillValue.isText()) {
                // Using toString of AutofillValue.getTextValue in order to save it to
                // SharedPreferences.
                textValue = autofillValue.getTextValue().toString();
            } else if (autofillValue.isDate()) {
                dateValue = autofillValue.getDateValue();
            } else if (autofillValue.isList()) {
                autofillOptions = viewNode.getAutofillOptions();
                listIndex = autofillValue.getListValue();
            } else if (autofillValue.isToggle()) {
                toggleValue = autofillValue.getToggleValue();
            }
        }
        appendViewMetadata(datasetWithFilledAutofillFields,
                hints, partition, textValue, dateValue, toggleValue,
                autofillOptions, listIndex);
    }

    private void appendViewMetadata(@NonNull DatasetWithFilledAutofillFields
            datasetWithFilledAutofillFields, @NonNull String[] hints, int partition,
            @Nullable String textValue, @Nullable Long dateValue, @Nullable Boolean toggleValue,
            @Nullable CharSequence[] autofillOptions, @Nullable Integer listIndex) {
        for (int i = 0; i < hints.length; i++) {
            String hint = hints[i];
            // Then check if the "actual" hint is supported.
            FieldTypeWithHeuristics fieldTypeWithHeuristics = mFieldTypesByAutofillHint.get(hint);
            if (fieldTypeWithHeuristics != null) {
                FieldType fieldType = fieldTypeWithHeuristics.fieldType;
                if (!AutofillHints.matchesPartition(fieldType.getPartition(), partition)) {
                    continue;
                }
                    // Only add the field if the hint is supported by the type.
                if (textValue != null) {
                        if (!fieldType.getAutofillTypes().ints.contains(View.AUTOFILL_TYPE_TEXT)) {
                            loge("Text is invalid type for hint '%s'", hint);
                        }
                    }
                if (autofillOptions != null && listIndex != null &&
                        autofillOptions.length > listIndex) {
                    if (!fieldType.getAutofillTypes().ints.contains(View.AUTOFILL_TYPE_LIST)) {
                        loge("List is invalid type for hint '%s'", hint);
                    }
                    textValue = autofillOptions[listIndex].toString();
                }
                if (dateValue != null) {
                    if (!fieldType.getAutofillTypes().ints.contains(View.AUTOFILL_TYPE_DATE)) {
                        loge("Date is invalid type for hint '%s'", hint);
                    }
                }
                if (toggleValue != null) {
                    if (!fieldType.getAutofillTypes().ints.contains(View.AUTOFILL_TYPE_TOGGLE)) {
                        loge("Toggle is invalid type for hint '%s'", hint);
                    }
                }
                String datasetId = datasetWithFilledAutofillFields.autofillDataset.getId();
                datasetWithFilledAutofillFields.add(new FilledAutofillField(datasetId,
                        fieldType.getTypeName(), textValue, dateValue, toggleValue));
            } else {
                loge("Invalid hint: %s", hint);
            }
        }
    }
} 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 interface PackageVerificationDataSource {

    /**
     * Verifies that the signatures in the passed {@code Context} match what is currently in
     * storage. If there are no current signatures in storage for this packageName, it will store
     * the signatures from the passed {@code Context}.
     *
     * @return {@code true} if signatures for this packageName are not currently in storage
     * or if the signatures in the passed {@code Context} match what is currently in storage;
     * {@code false} if the signatures in the passed {@code Context} do not match what is
     * currently in storage or if an {@code Exception} was thrown while generating the signatures.
     */
    boolean putPackageSignatures(String packageName);

    /**
     * Clears all signature data currently in storage.
     */
    void clear();
} 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 DigitalAssetLinksRepository implements DigitalAssetLinksDataSource {
    private static final String DAL_BASE_URL = "https://digitalassetlinks.googleapis.com";
    private static final String PERMISSION_GET_LOGIN_CREDS = "common.get_login_creds";
    private static final String PERMISSION_HANDLE_ALL_URLS = "common.handle_all_urls";
    private static DigitalAssetLinksRepository sInstance;

    private final PackageManager mPackageManager;
    private final DalService mDalService;
    private final HashMap<DalInfo, DalCheck> mCache;

    private DigitalAssetLinksRepository(PackageManager packageManager) {
        mPackageManager = packageManager;
        mCache = new HashMap<>();
        mDalService = new Retrofit.Builder()
                .baseUrl(DAL_BASE_URL)
                .build()
                .create(DalService.class);
    }

    public static DigitalAssetLinksRepository getInstance(PackageManager packageManager) {
        if (sInstance == null) {
            sInstance = new DigitalAssetLinksRepository(packageManager);
        }
        return sInstance;
    }

    public static String getCanonicalDomain(String domain) {
        InternetDomainName idn = InternetDomainName.from(domain);
        while (idn != null && !idn.isTopPrivateDomain()) {
            idn = idn.parent();
        }
        return idn == null ? null : idn.toString();
    }

    @Override
    public void clear() {
        mCache.clear();
    }

    public void checkValid(DalCheckRequirement dalCheckRequirement, DalInfo dalInfo,
            DataCallback<DalCheck> dalCheckDataCallback) {
        if (dalCheckRequirement.equals(Disabled)) {
            DalCheck dalCheck = new DalCheck();
            dalCheck.linked = true;
            dalCheckDataCallback.onLoaded(dalCheck);
            return;
        }

        DalCheck dalCheck = mCache.get(dalInfo);
        if (dalCheck != null) {
            dalCheckDataCallback.onLoaded(dalCheck);
            return;
        }
        String packageName = dalInfo.getPackageName();
        String webDomain = dalInfo.getWebDomain();

        final String fingerprint;
        try {
            PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
                    PackageManager.GET_SIGNATURES);
            fingerprint = SecurityHelper.getFingerprint(packageInfo, packageName);
        } catch (Exception e) {
            dalCheckDataCallback.onDataNotAvailable("Error getting fingerprint for %s",
                    packageName);
            return;
        }
        logd("validating domain %s for pkg %s and fingerprint %s.", webDomain,
                packageName, fingerprint);
        mDalService.check(webDomain, PERMISSION_GET_LOGIN_CREDS, packageName, fingerprint).enqueue(
                new Callback<DalCheck>() {
                    @Override
                    public void onResponse(@NonNull Call<DalCheck> call,
                            @NonNull Response<DalCheck> response) {
                        DalCheck dalCheck = response.body();
                        if (dalCheck == null || !dalCheck.linked) {
                            // get_login_creds check failed, so try handle_all_urls check
                            if (dalCheckRequirement.equals(LoginOnly)) {
                                dalCheckDataCallback.onDataNotAvailable(
                                        "DAL: Login creds check failed.");
                            } else if (dalCheckRequirement.equals(AllUrls)) {
                                mDalService.check(webDomain, PERMISSION_HANDLE_ALL_URLS,
                                        packageName, fingerprint).enqueue(new Callback<DalCheck>() {
                                    @Override
                                    public void onResponse(@NonNull Call<DalCheck> call,
                                            @NonNull Response<DalCheck> response) {
                                        DalCheck dalCheck = response.body();
                                        mCache.put(dalInfo, dalCheck);
                                        dalCheckDataCallback.onLoaded(dalCheck);
                                    }

                                    @Override
                                    public void onFailure(@NonNull Call<DalCheck> call,
                                            @NonNull Throwable t) {
                                        dalCheckDataCallback.onDataNotAvailable(t.getMessage());
                                    }
                                });
                            }
                        } else {
                            // get_login_creds check succeeded, so we're finished.
                            mCache.put(dalInfo, dalCheck);
                            dalCheckDataCallback.onLoaded(dalCheck);
                        }
                    }

                    @Override
                    public void onFailure(@NonNull Call<DalCheck> call, @NonNull Throwable t) {
                        // get_login_creds check failed, so try handle_all_urls check.
                        mDalService.check(webDomain, PERMISSION_HANDLE_ALL_URLS, packageName,
                                fingerprint);
                    }
                });
    }
} 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();
    }
} public class SharedPrefsPackageVerificationRepository implements PackageVerificationDataSource {

    private static final String SHARED_PREF_KEY = "com.example.android.autofill.service"
            + ".datasource.PackageVerificationDataSource";
    private static PackageVerificationDataSource sInstance;

    private final SharedPreferences mSharedPrefs;
    private final Context mContext;

    private SharedPrefsPackageVerificationRepository(Context context) {
        mSharedPrefs = context.getApplicationContext()
                .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE);
        mContext = context.getApplicationContext();
    }

    public static PackageVerificationDataSource getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new SharedPrefsPackageVerificationRepository(
                    context.getApplicationContext());
        }
        return sInstance;
    }

    @Override
    public void clear() {
        mSharedPrefs.edit().clear().apply();
    }

    @Override
    public boolean putPackageSignatures(String packageName) {
        String hash;
        try {
            PackageManager pm = mContext.getPackageManager();
            PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
            hash = SecurityHelper.getFingerprint(packageInfo, packageName);
            logd("Hash for %s: %s", packageName, hash);
        } catch (Exception e) {
            logw(e, "Error getting hash for %s.", packageName);
            return false;
        }

        if (!containsSignatureForPackage(packageName)) {
            // Storage does not yet contain signature for this package name.
            mSharedPrefs.edit().putString(packageName, hash).apply();
            return true;
        }
        return containsMatchingSignatureForPackage(packageName, hash);
    }

    private boolean containsSignatureForPackage(String packageName) {
        return mSharedPrefs.contains(packageName);
    }

    private boolean containsMatchingSignatureForPackage(String packageName,
            String hash) {
        return hash.equals(mSharedPrefs.getString(packageName, null));
    }
} @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();
} public class DalCheck {
    public boolean linked;
    public String maxAge;
    public String debugString;
} public class DalInfo {
    private final String mWebDomain;
    private final String mPackageName;

    public DalInfo(String webDomain, String packageName) {
        String canonicalDomain = getCanonicalDomain(webDomain);
        final String fullDomain;
        if (!webDomain.startsWith("http:") && !webDomain.startsWith("https:")) {
            // Unfortunately AssistStructure.ViewNode does not tell what the domain is, so let's
            // assume it's https
            fullDomain = "https://" + canonicalDomain;
        } else {
            fullDomain = canonicalDomain;
        }
        mWebDomain = fullDomain;
        mPackageName = packageName;
    }

    public String getWebDomain() {
        return mWebDomain;
    }

    public String getPackageName() {
        return mPackageName;
    }

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

        DalInfo dalInfo = (DalInfo) o;

        if (mWebDomain != null ? !mWebDomain.equals(dalInfo.mWebDomain) :
                dalInfo.mWebDomain != null)
            return false;
        return mPackageName != null ? mPackageName.equals(dalInfo.mPackageName) :
                dalInfo.mPackageName == null;
    }

    @Override
    public int hashCode() {
        int result = mWebDomain != null ? mWebDomain.hashCode() : 0;
        result = 31 * result + (mPackageName != null ? mPackageName.hashCode() : 0);
        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;
    }
} 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;
    }
} 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 final class Util {

    public static final String EXTRA_DATASET_NAME = "dataset_name";
    public static final String EXTRA_FOR_RESPONSE = "for_response";
    public static final NodeFilter AUTOFILL_ID_FILTER = (node, id) ->
            id.equals(node.getAutofillId());
    private static final String TAG = "AutofillSample";
    public static LogLevel sLoggingLevel = LogLevel.Off;

    private static void bundleToString(StringBuilder builder, Bundle data) {
        final Set<String> keySet = data.keySet();
        builder.append("[Bundle with ").append(keySet.size()).append(" keys:");
        for (String key : keySet) {
            builder.append(' ').append(key).append('=');
            Object value = data.get(key);
            if ((value instanceof Bundle)) {
                bundleToString(builder, (Bundle) value);
            } else {
                builder.append((value instanceof Object[])
                        ? Arrays.toString((Object[]) value) : value);
            }
        }
        builder.append(']');
    }

    public static String bundleToString(Bundle data) {
        if (data == null) {
            return "N/A";
        }
        final StringBuilder builder = new StringBuilder();
        bundleToString(builder, data);
        return builder.toString();
    }

    public static String getTypeAsString(int type) {
        switch (type) {
            case View.AUTOFILL_TYPE_TEXT:
                return "TYPE_TEXT";
            case View.AUTOFILL_TYPE_LIST:
                return "TYPE_LIST";
            case View.AUTOFILL_TYPE_NONE:
                return "TYPE_NONE";
            case View.AUTOFILL_TYPE_TOGGLE:
                return "TYPE_TOGGLE";
            case View.AUTOFILL_TYPE_DATE:
                return "TYPE_DATE";
        }
        return "UNKNOWN_TYPE";
    }

    private static String getAutofillValueAndTypeAsString(AutofillValue value) {
        if (value == null) return "null";

        StringBuilder builder = new StringBuilder(value.toString()).append('(');
        if (value.isText()) {
            builder.append("isText");
        } else if (value.isDate()) {
            builder.append("isDate");
        } else if (value.isToggle()) {
            builder.append("isToggle");
        } else if (value.isList()) {
            builder.append("isList");
        }
        return builder.append(')').toString();
    }

    public static void dumpStructure(AssistStructure structure) {
        if (logVerboseEnabled()) {
            int nodeCount = structure.getWindowNodeCount();
            logv("dumpStructure(): component=%s numberNodes=%d",
                    structure.getActivityComponent(), nodeCount);
            for (int i = 0; i < nodeCount; i++) {
                logv("node #%d", i);
                WindowNode node = structure.getWindowNodeAt(i);
                dumpNode(new StringBuilder(), "  ", node.getRootViewNode(), 0);
            }
        }
    }

    private static void dumpNode(StringBuilder builder, String prefix, ViewNode node, int childNumber) {
        builder.append(prefix)
                .append("child #").append(childNumber).append("\n");

        builder.append(prefix)
                .append("autoFillId: ").append(node.getAutofillId())
                .append("\tidEntry: ").append(node.getIdEntry())
                .append("\tid: ").append(node.getId())
                .append("\tclassName: ").append(node.getClassName())
                .append('\n');

        builder.append(prefix)
                .append("focused: ").append(node.isFocused())
                .append("\tvisibility").append(node.getVisibility())
                .append("\tchecked: ").append(node.isChecked())
                .append("\twebDomain: ").append(node.getWebDomain())
                .append("\thint: ").append(node.getHint())
                .append('\n');

        HtmlInfo htmlInfo = node.getHtmlInfo();

        if (htmlInfo != null) {
            builder.append(prefix)
                    .append("HTML TAG: ").append(htmlInfo.getTag())
                    .append(" attrs: ").append(htmlInfo.getAttributes())
                    .append('\n');
        }

        String[] afHints = node.getAutofillHints();
        CharSequence[] options = node.getAutofillOptions();
        builder.append(prefix).append("afType: ").append(getTypeAsString(node.getAutofillType()))
                .append("\tafValue:")
                .append(getAutofillValueAndTypeAsString(node.getAutofillValue()))
                .append("\tafOptions:").append(options == null ? "N/A" : Arrays.toString(options))
                .append("\tafHints: ").append(afHints == null ? "N/A" : Arrays.toString(afHints))
                .append("\tinputType:").append(node.getInputType())
                .append('\n');

        int numberChildren = node.getChildCount();
        builder.append(prefix).append("# children: ").append(numberChildren)
                .append("\ttext: ").append(node.getText())
                .append('\n');

        final String prefix2 = prefix + "  ";
        for (int i = 0; i < numberChildren; i++) {
            dumpNode(builder, prefix2, node.getChildAt(i), i);
        }
        logv(builder.toString());
    }

    public static String getSaveTypeAsString(int type) {
        List<String> types = new ArrayList<>();
        if ((type & SaveInfo.SAVE_DATA_TYPE_ADDRESS) != 0) {
            types.add("ADDRESS");
        }
        if ((type & SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD) != 0) {
            types.add("CREDIT_CARD");
        }
        if ((type & SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS) != 0) {
            types.add("EMAIL_ADDRESS");
        }
        if ((type & SaveInfo.SAVE_DATA_TYPE_USERNAME) != 0) {
            types.add("USERNAME");
        }
        if ((type & SaveInfo.SAVE_DATA_TYPE_PASSWORD) != 0) {
            types.add("PASSWORD");
        }
        if (types.isEmpty()) {
            return "UNKNOWN(" + type + ")";
        }
        return Joiner.on('|').join(types);
    }

    /**
     * Gets a node if it matches the filter criteria for the given id.
     */
    public static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id,
            @NonNull NodeFilter filter) {
        for (FillContext context : contexts) {
            ViewNode node = findNodeByFilter(context.getStructure(), id, filter);
            if (node != null) {
                return node;
            }
        }
        return null;
    }

    /**
     * Gets a node if it matches the filter criteria for the given id.
     */
    public static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id,
            @NonNull NodeFilter filter) {
        logv("Parsing request for activity %s", structure.getActivityComponent());
        final int nodes = structure.getWindowNodeCount();
        for (int i = 0; i < nodes; i++) {
            final WindowNode windowNode = structure.getWindowNodeAt(i);
            final ViewNode rootNode = windowNode.getRootViewNode();
            final ViewNode node = findNodeByFilter(rootNode, id, filter);
            if (node != null) {
                return node;
            }
        }
        return null;
    }

    /**
     * Gets a node if it matches the filter criteria for the given id.
     */
    public static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id,
            @NonNull NodeFilter filter) {
        if (filter.matches(node, id)) {
            return node;
        }
        final int childrenSize = node.getChildCount();
        if (childrenSize > 0) {
            for (int i = 0; i < childrenSize; i++) {
                final ViewNode found = findNodeByFilter(node.getChildAt(i), id, filter);
                if (found != null) {
                    return found;
                }
            }
        }
        return null;
    }

    public static void logd(String message, Object... params) {
        if (logDebugEnabled()) {
            Log.d(TAG, String.format(message, params));
        }
    }

    public static void logv(String message, Object... params) {
        if (logVerboseEnabled()) {
            Log.v(TAG, String.format(message, params));
        }
    }

    public static boolean logDebugEnabled() {
        return sLoggingLevel.ordinal() >= LogLevel.Debug.ordinal();
    }

    public static boolean logVerboseEnabled() {
        return sLoggingLevel.ordinal() >= LogLevel.Verbose.ordinal();
    }

    public static void logw(String message, Object... params) {
        Log.w(TAG, String.format(message, params));
    }

    public static void logw(Throwable throwable, String message, Object... params) {
        Log.w(TAG, String.format(message, params), throwable);
    }

    public static void loge(String message, Object... params) {
        Log.e(TAG, String.format(message, params));
    }

    public static void loge(Throwable throwable, String message, Object... params) {
        Log.e(TAG, String.format(message, params), throwable);
    }

    public static void setLoggingLevel(LogLevel level) {
        sLoggingLevel = level;
    }

    /**
     * Helper method for getting the index of a CharSequence object in an array.
     */
    public static int indexOf(@NonNull CharSequence[] array, CharSequence charSequence) {
        int index = -1;
        if (charSequence == null) {
            return index;
        }
        for (int i = 0; i < array.length; i++) {
            if (charSequence.equals(array[i])) {
                index = i;
                break;
            }
        }
        return index;
    }

    public enum LogLevel {Off, Debug, Verbose}

    public enum DalCheckRequirement {Disabled, LoginOnly, AllUrls}

    /**
     * Helper interface used to filter Assist nodes.
     */
    public interface NodeFilter {
        /**
         * Returns whether the node passes the filter for such given id.
         */
        boolean matches(ViewNode node, Object id);
    }
} public enum DalCheckRequirement {Disabled, LoginOnly, AllUrls} private static void bundleToString(StringBuilder builder, Bundle data) {
    final Set<String> keySet = data.keySet();
    builder.append("[Bundle with ").append(keySet.size()).append(" keys:");
    for (String key : keySet) {
        builder.append(' ').append(key).append('=');
        Object value = data.get(key);
        if ((value instanceof Bundle)) {
            bundleToString(builder, (Bundle) value);
        } else {
            builder.append((value instanceof Object[])
                    ? Arrays.toString((Object[]) value) : value);
        }
    }
    builder.append(']');
} public static void dumpStructure(AssistStructure structure) {
    if (logVerboseEnabled()) {
        int nodeCount = structure.getWindowNodeCount();
        logv("dumpStructure(): component=%s numberNodes=%d",
                structure.getActivityComponent(), nodeCount);
        for (int i = 0; i < nodeCount; i++) {
            logv("node #%d", i);
            WindowNode node = structure.getWindowNodeAt(i);
            dumpNode(new StringBuilder(), "  ", node.getRootViewNode(), 0);
        }
    }
} public static boolean logVerboseEnabled() {
    return sLoggingLevel.ordinal() >= LogLevel.Verbose.ordinal();
} public static void logd(String message, Object... params) {
    if (logDebugEnabled()) {
        Log.d(TAG, String.format(message, params));
    }
} public static void loge(String message, Object... params) {
    Log.e(TAG, String.format(message, params));
} public static void logv(String message, Object... params) {
    if (logVerboseEnabled()) {
        Log.v(TAG, String.format(message, params));
    }
} public static void logw(String message, Object... params) {
    Log.w(TAG, String.format(message, params));
}
import android.app.assist.AssistStructure;
import android.content.Context;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.os.CancellationSignal;
import android.service.autofill.AutofillService;
import android.service.autofill.FillCallback;
import android.service.autofill.FillContext;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.service.autofill.SaveCallback;
import android.service.autofill.SaveRequest;
import android.support.annotation.NonNull;
import android.view.autofill.AutofillManager;
import android.widget.RemoteViews;
import com.example.android.autofill.service.data.AutofillDataBuilder;
import com.example.android.autofill.service.data.ClientAutofillDataBuilder;
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.PackageVerificationDataSource;
import com.example.android.autofill.service.data.source.local.DefaultFieldTypesLocalJsonSource;
import com.example.android.autofill.service.data.source.local.DigitalAssetLinksRepository;
import com.example.android.autofill.service.data.source.local.LocalAutofillDataSource;
import com.example.android.autofill.service.data.source.local.SharedPrefsPackageVerificationRepository;
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.DalCheck;
import com.example.android.autofill.service.model.DalInfo;
import com.example.android.autofill.service.model.DatasetWithFilledAutofillFields;
import com.example.android.autofill.service.model.FieldTypeWithHeuristics;
import com.example.android.autofill.service.settings.MyPreferences;
import com.example.android.autofill.service.util.AppExecutors;
import com.example.android.autofill.service.util.Util;
import com.google.gson.GsonBuilder;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static com.example.android.autofill.service.util.Util.DalCheckRequirement;
import static com.example.android.autofill.service.util.Util.bundleToString;
import static com.example.android.autofill.service.util.Util.dumpStructure;
import static com.example.android.autofill.service.util.Util.logVerboseEnabled;
import static com.example.android.autofill.service.util.Util.logd;
import static com.example.android.autofill.service.util.Util.loge;
import static com.example.android.autofill.service.util.Util.logv;
import static com.example.android.autofill.service.util.Util.logw;
import static java.util.stream.Collectors.toList;
/*
 * Copyright (C) 2017 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;





public class MyAutofillService extends AutofillService {

    private LocalAutofillDataSource mLocalAutofillDataSource;
    private DigitalAssetLinksRepository mDalRepository;
    private PackageVerificationDataSource mPackageVerificationRepository;
    private AutofillDataBuilder mAutofillDataBuilder;
    private ResponseAdapter mResponseAdapter;
    private ClientViewMetadata mClientViewMetadata;
    private MyPreferences mPreferences;

    @Override
    public void onCreate() {
        super.onCreate();
        mPreferences = MyPreferences.getInstance(this);
        Util.setLoggingLevel(mPreferences.getLoggingLevel());
        SharedPreferences localAfDataSourceSharedPrefs =
                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(localAfDataSourceSharedPrefs,
                autofillDao, new AppExecutors());
        mDalRepository = DigitalAssetLinksRepository.getInstance(getPackageManager());
        mPackageVerificationRepository = SharedPrefsPackageVerificationRepository.getInstance(this);
    }

    @Override
    public void onFillRequest(@NonNull FillRequest request,
            @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback) {
        List<FillContext> fillContexts = request.getFillContexts();
        List<AssistStructure> structures =
                fillContexts.stream().map(FillContext::getStructure).collect(toList());
        AssistStructure latestStructure = fillContexts.get(fillContexts.size() - 1).getStructure();
        ClientParser parser = new ClientParser(structures);

        // Check user's settings for authenticating Responses and Datasets.
        boolean responseAuth = mPreferences.isResponseAuth();
        boolean datasetAuth = mPreferences.isDatasetAuth();
        boolean manual = (request.getFlags() & FillRequest.FLAG_MANUAL_REQUEST) != 0;
        mLocalAutofillDataSource.getFieldTypeByAutofillHints(
                new DataCallback<HashMap<String, FieldTypeWithHeuristics>>() {