1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.example.android.inlinefillservice; 17 18 import android.content.Context; 19 import android.content.IntentSender; 20 import android.os.CancellationSignal; 21 import android.service.autofill.AutofillService; 22 import android.service.autofill.FillCallback; 23 import android.service.autofill.FillRequest; 24 import android.service.autofill.FillResponse; 25 import android.service.autofill.InlinePresentation; 26 import android.service.autofill.Presentations; 27 import android.service.autofill.SaveCallback; 28 import android.service.autofill.SaveInfo; 29 import android.service.autofill.SaveRequest; 30 import android.service.autofill.SavedDatasetsInfo; 31 import android.service.autofill.SavedDatasetsInfoCallback; 32 import android.util.ArrayMap; 33 import android.util.Log; 34 import android.view.autofill.AutofillId; 35 import android.view.inputmethod.InlineSuggestionsRequest; 36 import android.widget.RemoteViews; 37 38 import androidx.annotation.NonNull; 39 40 import java.util.Collection; 41 import java.util.Collections; 42 import java.util.Optional; 43 44 /** 45 * A basic {@link AutofillService} implementation that only shows dynamic-generated datasets 46 * and supports inline suggestions. 47 */ 48 public class InlineFillService extends AutofillService { 49 50 static final String TAG = "InlineFillService"; 51 52 /** 53 * Number of datasets sent on each request - we're simple, that value is hardcoded in our DNA! 54 */ 55 static final int NUMBER_DATASETS = 6; 56 57 private final boolean mAuthenticateResponses = false; 58 private final boolean mAuthenticateDatasets = false; 59 60 @Override onFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback)61 public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal, 62 FillCallback callback) { 63 Log.d(TAG, "onFillRequest()"); 64 65 final Context context = getApplicationContext(); 66 67 // Find autofillable fields 68 ArrayMap<String, AutofillId> fields = Helper.getAutofillableFields(request); 69 Log.d(TAG, "autofillable fields:" + fields); 70 if (fields.isEmpty()) { 71 Helper.showMessage(context, 72 "InlineFillService could not figure out how to autofill this screen"); 73 callback.onSuccess(null); 74 return; 75 } 76 final Optional<InlineSuggestionsRequest> inlineRequest = 77 InlineRequestHelper.getInlineSuggestionsRequest(request); 78 final int maxSuggestionsCount = InlineRequestHelper.getMaxSuggestionCount(inlineRequest, 79 NUMBER_DATASETS); 80 81 // Create the base response 82 final FillResponse response; 83 if (mAuthenticateResponses) { 84 int size = fields.size(); 85 String[] hints = new String[size]; 86 AutofillId[] ids = new AutofillId[size]; 87 for (int i = 0; i < size; i++) { 88 hints[i] = fields.keyAt(i); 89 ids[i] = fields.valueAt(i); 90 } 91 IntentSender authentication = AuthActivity.newIntentSenderForResponse(this, hints, 92 ids, mAuthenticateDatasets, inlineRequest.orElse(null)); 93 RemoteViews presentation = ResponseHelper.newDatasetPresentation(getPackageName(), 94 "Tap to auth response"); 95 96 InlinePresentation inlinePresentation = 97 InlineRequestHelper.maybeCreateInlineAuthenticationResponse(context, 98 inlineRequest); 99 final Presentations.Builder fieldPresentationsBuilder = 100 new Presentations.Builder(); 101 fieldPresentationsBuilder.setMenuPresentation(presentation); 102 fieldPresentationsBuilder.setInlinePresentation(inlinePresentation); 103 response = new FillResponse.Builder() 104 .setAuthentication(ids, authentication, fieldPresentationsBuilder.build()) 105 .build(); 106 } else { 107 response = createResponse(this, fields, maxSuggestionsCount, mAuthenticateDatasets, 108 inlineRequest); 109 } 110 callback.onSuccess(response); 111 } 112 createResponse(@onNull Context context, @NonNull ArrayMap<String, AutofillId> fields, int numDatasets, boolean authenticateDatasets, @NonNull Optional<InlineSuggestionsRequest> inlineRequest)113 static FillResponse createResponse(@NonNull Context context, 114 @NonNull ArrayMap<String, AutofillId> fields, int numDatasets, 115 boolean authenticateDatasets, 116 @NonNull Optional<InlineSuggestionsRequest> inlineRequest) { 117 String packageName = context.getPackageName(); 118 FillResponse.Builder response = new FillResponse.Builder(); 119 // 1.Add the dynamic datasets 120 for (int i = 0; i < numDatasets; i++) { 121 if (authenticateDatasets) { 122 response.addDataset(ResponseHelper.newLockedDataset(context, fields, packageName, i, 123 inlineRequest)); 124 } else { 125 response.addDataset(ResponseHelper.newUnlockedDataset(context, fields, 126 packageName, i, inlineRequest)); 127 } 128 } 129 130 // 2. Add some inline actions 131 if (inlineRequest.isPresent()) { 132 response.addDataset(InlineRequestHelper.createInlineActionDataset(context, fields, 133 inlineRequest.get(), R.drawable.ic_settings)); 134 response.addDataset(InlineRequestHelper.createInlineActionDataset(context, fields, 135 inlineRequest.get(), R.drawable.ic_settings)); 136 } 137 // 3. Add fill dialog 138 RemoteViews dialogPresentation = 139 ResponseHelper.newDatasetPresentation(packageName, "Dialog Header"); 140 response.setDialogHeader(dialogPresentation); 141 response.setFillDialogTriggerIds(fields.valueAt(0), fields.valueAt(1)); 142 143 // 4.Add save info 144 Collection<AutofillId> ids = fields.values(); 145 AutofillId[] requiredIds = new AutofillId[ids.size()]; 146 ids.toArray(requiredIds); 147 response.setSaveInfo( 148 // We're simple, so we're generic 149 new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, requiredIds).build()); 150 151 // 5.Profit! 152 return response.build(); 153 } 154 155 @Override onSaveRequest(SaveRequest request, SaveCallback callback)156 public void onSaveRequest(SaveRequest request, SaveCallback callback) { 157 Log.d(TAG, "onSaveRequest()"); 158 Helper.showMessage(getApplicationContext(), "InlineFillService doesn't support Save"); 159 callback.onSuccess(); 160 } 161 162 @Override onSavedDatasetsInfoRequest(@onNull SavedDatasetsInfoCallback callback)163 public void onSavedDatasetsInfoRequest(@NonNull SavedDatasetsInfoCallback callback) { 164 callback.onSuccess( 165 Collections.singleton( 166 new SavedDatasetsInfo( 167 SavedDatasetsInfo.TYPE_PASSWORDS, sNumSavedDatasets))); 168 sNumSavedDatasets++; 169 sNumSavedDatasets %= 3; 170 } 171 172 private static int sNumSavedDatasets = 0; 173 } 174