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.SaveCallback; 27 import android.service.autofill.SaveInfo; 28 import android.service.autofill.SaveRequest; 29 import android.service.autofill.SavedDatasetsInfo; 30 import android.service.autofill.SavedDatasetsInfoCallback; 31 import android.util.ArrayMap; 32 import android.util.Log; 33 import android.view.autofill.AutofillId; 34 import android.view.inputmethod.InlineSuggestionsRequest; 35 import android.widget.RemoteViews; 36 37 import androidx.annotation.NonNull; 38 39 import java.util.Collection; 40 import java.util.Collections; 41 import java.util.Optional; 42 43 /** 44 * A basic {@link AutofillService} implementation that only shows dynamic-generated datasets 45 * and supports inline suggestions. 46 */ 47 public class InlineFillService extends AutofillService { 48 49 static final String TAG = "InlineFillService"; 50 51 /** 52 * Number of datasets sent on each request - we're simple, that value is hardcoded in our DNA! 53 */ 54 static final int NUMBER_DATASETS = 6; 55 56 private final boolean mAuthenticateResponses = false; 57 private final boolean mAuthenticateDatasets = false; 58 59 @Override onFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback)60 public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal, 61 FillCallback callback) { 62 Log.d(TAG, "onFillRequest()"); 63 64 final Context context = getApplicationContext(); 65 66 // Find autofillable fields 67 ArrayMap<String, AutofillId> fields = Helper.getAutofillableFields(request); 68 Log.d(TAG, "autofillable fields:" + fields); 69 if (fields.isEmpty()) { 70 Helper.showMessage(context, 71 "InlineFillService could not figure out how to autofill this screen"); 72 callback.onSuccess(null); 73 return; 74 } 75 final Optional<InlineSuggestionsRequest> inlineRequest = 76 InlineRequestHelper.getInlineSuggestionsRequest(request); 77 final int maxSuggestionsCount = InlineRequestHelper.getMaxSuggestionCount(inlineRequest, 78 NUMBER_DATASETS); 79 80 // Create the base response 81 final FillResponse response; 82 if (mAuthenticateResponses) { 83 int size = fields.size(); 84 String[] hints = new String[size]; 85 AutofillId[] ids = new AutofillId[size]; 86 for (int i = 0; i < size; i++) { 87 hints[i] = fields.keyAt(i); 88 ids[i] = fields.valueAt(i); 89 } 90 IntentSender authentication = AuthActivity.newIntentSenderForResponse(this, hints, 91 ids, mAuthenticateDatasets, inlineRequest.orElse(null)); 92 RemoteViews presentation = ResponseHelper.newDatasetPresentation(getPackageName(), 93 "Tap to auth response"); 94 95 InlinePresentation inlinePresentation = 96 InlineRequestHelper.maybeCreateInlineAuthenticationResponse(context, 97 inlineRequest); 98 response = new FillResponse.Builder() 99 .setAuthentication(ids, authentication, presentation, inlinePresentation) 100 .build(); 101 } else { 102 response = createResponse(this, fields, maxSuggestionsCount, mAuthenticateDatasets, 103 inlineRequest); 104 } 105 106 callback.onSuccess(response); 107 } 108 createResponse(@onNull Context context, @NonNull ArrayMap<String, AutofillId> fields, int numDatasets, boolean authenticateDatasets, @NonNull Optional<InlineSuggestionsRequest> inlineRequest)109 static FillResponse createResponse(@NonNull Context context, 110 @NonNull ArrayMap<String, AutofillId> fields, int numDatasets, 111 boolean authenticateDatasets, 112 @NonNull Optional<InlineSuggestionsRequest> inlineRequest) { 113 String packageName = context.getPackageName(); 114 FillResponse.Builder response = new FillResponse.Builder(); 115 // 1.Add the dynamic datasets 116 for (int i = 0; i < numDatasets; i++) { 117 if (authenticateDatasets) { 118 response.addDataset(ResponseHelper.newLockedDataset(context, fields, packageName, i, 119 inlineRequest)); 120 } else { 121 response.addDataset(ResponseHelper.newUnlockedDataset(context, fields, 122 packageName, i, inlineRequest)); 123 } 124 } 125 126 // 2. Add some inline actions 127 if (inlineRequest.isPresent()) { 128 response.addDataset(InlineRequestHelper.createInlineActionDataset(context, fields, 129 inlineRequest.get(), R.drawable.ic_settings)); 130 response.addDataset(InlineRequestHelper.createInlineActionDataset(context, fields, 131 inlineRequest.get(), R.drawable.ic_settings)); 132 } 133 134 // 3.Add save info 135 Collection<AutofillId> ids = fields.values(); 136 AutofillId[] requiredIds = new AutofillId[ids.size()]; 137 ids.toArray(requiredIds); 138 response.setSaveInfo( 139 // We're simple, so we're generic 140 new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, requiredIds).build()); 141 142 // 4.Profit! 143 return response.build(); 144 } 145 146 @Override onSaveRequest(SaveRequest request, SaveCallback callback)147 public void onSaveRequest(SaveRequest request, SaveCallback callback) { 148 Log.d(TAG, "onSaveRequest()"); 149 Helper.showMessage(getApplicationContext(), "InlineFillService doesn't support Save"); 150 callback.onSuccess(); 151 } 152 153 @Override onSavedDatasetsInfoRequest(@onNull SavedDatasetsInfoCallback callback)154 public void onSavedDatasetsInfoRequest(@NonNull SavedDatasetsInfoCallback callback) { 155 callback.onSuccess( 156 Collections.singleton( 157 new SavedDatasetsInfo( 158 SavedDatasetsInfo.TYPE_PASSWORDS, sNumSavedDatasets))); 159 sNumSavedDatasets++; 160 sNumSavedDatasets %= 3; 161 } 162 163 private static int sNumSavedDatasets = 0; 164 } 165