| /* | |
| * Copyright (C) 2019 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.inlinefillservice; | |
| import android.content.Context; | |
| import android.content.IntentSender; | |
| import android.os.CancellationSignal; | |
| import android.service.autofill.AutofillService; | |
| import android.service.autofill.FillCallback; | |
| import android.service.autofill.FillRequest; | |
| import android.service.autofill.FillResponse; | |
| import android.service.autofill.InlinePresentation; | |
| import android.service.autofill.Presentations; | |
| import android.service.autofill.SaveCallback; | |
| import android.service.autofill.SaveInfo; | |
| import android.service.autofill.SaveRequest; | |
| import android.service.autofill.SavedDatasetsInfo; | |
| import android.service.autofill.SavedDatasetsInfoCallback; | |
| import android.util.ArrayMap; | |
| import android.util.Log; | |
| import android.view.autofill.AutofillId; | |
| import android.view.inputmethod.InlineSuggestionsRequest; | |
| import android.widget.RemoteViews; | |
| import androidx.annotation.NonNull; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| import java.util.Optional; | |
| /** | |
| * A basic {@link AutofillService} implementation that only shows dynamic-generated datasets | |
| * and supports inline suggestions. | |
| */ | |
| public class InlineFillService extends AutofillService { | |
| static final String TAG = "InlineFillService"; | |
| /** | |
| * Number of datasets sent on each request - we're simple, that value is hardcoded in our DNA! | |
| */ | |
| static final int NUMBER_DATASETS = 6; | |
| private final boolean mAuthenticateResponses = false; | |
| private final boolean mAuthenticateDatasets = false; | |
| @Override | |
| public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal, | |
| FillCallback callback) { | |
| Log.d(TAG, "onFillRequest()"); | |
| final Context context = getApplicationContext(); | |
| // Find autofillable fields | |
| ArrayMap<String, AutofillId> fields = Helper.getAutofillableFields(request); | |
| Log.d(TAG, "autofillable fields:" + fields); | |
| if (fields.isEmpty()) { | |
| Helper.showMessage(context, | |
| "InlineFillService could not figure out how to autofill this screen"); | |
| callback.onSuccess(null); | |
| return; | |
| } | |
| final Optional<InlineSuggestionsRequest> inlineRequest = | |
| InlineRequestHelper.getInlineSuggestionsRequest(request); | |
| final int maxSuggestionsCount = InlineRequestHelper.getMaxSuggestionCount(inlineRequest, | |
| NUMBER_DATASETS); | |
| // Create the base response | |
| final FillResponse response; | |
| if (mAuthenticateResponses) { | |
| int size = fields.size(); | |
| String[] hints = new String[size]; | |
| AutofillId[] ids = new AutofillId[size]; | |
| for (int i = 0; i < size; i++) { | |
| hints[i] = fields.keyAt(i); | |
| ids[i] = fields.valueAt(i); | |
| } | |
| IntentSender authentication = AuthActivity.newIntentSenderForResponse(this, hints, | |
| ids, mAuthenticateDatasets, inlineRequest.orElse(null)); | |
| RemoteViews presentation = ResponseHelper.newDatasetPresentation(getPackageName(), | |
| "Tap to auth response"); | |
| InlinePresentation inlinePresentation = | |
| InlineRequestHelper.maybeCreateInlineAuthenticationResponse(context, | |
| inlineRequest); | |
| final Presentations.Builder fieldPresentationsBuilder = | |
| new Presentations.Builder(); | |
| fieldPresentationsBuilder.setMenuPresentation(presentation); | |
| fieldPresentationsBuilder.setInlinePresentation(inlinePresentation); | |
| response = new FillResponse.Builder() | |
| .setAuthentication(ids, authentication, fieldPresentationsBuilder.build()) | |
| .build(); | |
| } else { | |
| response = createResponse(this, fields, maxSuggestionsCount, mAuthenticateDatasets, | |
| inlineRequest); | |
| } | |
| callback.onSuccess(response); | |
| } | |
| static FillResponse createResponse(@NonNull Context context, | |
| @NonNull ArrayMap<String, AutofillId> fields, int numDatasets, | |
| boolean authenticateDatasets, | |
| @NonNull Optional<InlineSuggestionsRequest> inlineRequest) { | |
| String packageName = context.getPackageName(); | |
| FillResponse.Builder response = new FillResponse.Builder(); | |
| // 1.Add the dynamic datasets | |
| for (int i = 0; i < numDatasets; i++) { | |
| if (authenticateDatasets) { | |
| response.addDataset(ResponseHelper.newLockedDataset(context, fields, packageName, i, | |
| inlineRequest)); | |
| } else { | |
| response.addDataset(ResponseHelper.newUnlockedDataset(context, fields, | |
| packageName, i, inlineRequest)); | |
| } | |
| } | |
| // 2. Add some inline actions | |
| if (inlineRequest.isPresent()) { | |
| response.addDataset(InlineRequestHelper.createInlineActionDataset(context, fields, | |
| inlineRequest.get(), R.drawable.ic_settings)); | |
| response.addDataset(InlineRequestHelper.createInlineActionDataset(context, fields, | |
| inlineRequest.get(), R.drawable.ic_settings)); | |
| } | |
| // 3. Add fill dialog | |
| RemoteViews dialogPresentation = | |
| ResponseHelper.newDatasetPresentation(packageName, "Dialog Header"); | |
| response.setDialogHeader(dialogPresentation); | |
| response.setFillDialogTriggerIds(fields.valueAt(0), fields.valueAt(1)); | |
| // 4.Add save info | |
| Collection<AutofillId> ids = fields.values(); | |
| AutofillId[] requiredIds = new AutofillId[ids.size()]; | |
| ids.toArray(requiredIds); | |
| response.setSaveInfo( | |
| // We're simple, so we're generic | |
| new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, requiredIds).build()); | |
| // 5.Profit! | |
| return response.build(); | |
| } | |
| @Override | |
| public void onSaveRequest(SaveRequest request, SaveCallback callback) { | |
| Log.d(TAG, "onSaveRequest()"); | |
| Helper.showMessage(getApplicationContext(), "InlineFillService doesn't support Save"); | |
| callback.onSuccess(); | |
| } | |
| @Override | |
| public void onSavedDatasetsInfoRequest(@NonNull SavedDatasetsInfoCallback callback) { | |
| callback.onSuccess( | |
| Collections.singleton( | |
| new SavedDatasetsInfo( | |
| SavedDatasetsInfo.TYPE_PASSWORDS, sNumSavedDatasets))); | |
| sNumSavedDatasets++; | |
| sNumSavedDatasets %= 3; | |
| } | |
| private static int sNumSavedDatasets = 0; | |
| } |