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