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