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