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