1 /*
2  * Copyright (C) 2020 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 
17 package com.android.server.autofill.ui;
18 
19 import static android.service.autofill.FillResponse.FLAG_CREDENTIAL_MANAGER_RESPONSE;
20 import static android.view.inputmethod.InlineSuggestionInfo.TYPE_SUGGESTION;
21 
22 import static com.android.server.autofill.Helper.sDebug;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.content.IntentSender;
27 import android.service.autofill.Dataset;
28 import android.service.autofill.FillResponse;
29 import android.service.autofill.Flags;
30 import android.service.autofill.InlinePresentation;
31 import android.util.Pair;
32 import android.util.Slog;
33 import android.util.SparseArray;
34 import android.view.autofill.AutofillManager;
35 import android.view.inputmethod.InlineSuggestion;
36 import android.view.inputmethod.InlineSuggestionInfo;
37 import android.view.inputmethod.InlineSuggestionsRequest;
38 import android.widget.inline.InlinePresentationSpec;
39 
40 import com.android.internal.view.inline.IInlineContentProvider;
41 
42 import java.util.List;
43 
44 final class InlineSuggestionFactory {
45     private static final String TAG = "InlineSuggestionFactory";
46 
createInlineAuthentication( @onNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo, @NonNull FillResponse response, @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback)47     public static InlineSuggestion createInlineAuthentication(
48             @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo, @NonNull FillResponse response,
49             @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) {
50         InlinePresentation inlineAuthentication = response.getInlinePresentation();
51         final int requestId = response.getRequestId();
52         boolean ignoreHostSpec = Flags.autofillCredmanIntegration() && (
53                 (response.getFlags() & FLAG_CREDENTIAL_MANAGER_RESPONSE) != 0);
54 
55         return createInlineSuggestion(inlineFillUiInfo, InlineSuggestionInfo.SOURCE_AUTOFILL,
56                 InlineSuggestionInfo.TYPE_ACTION, () -> uiCallback.authenticate(requestId,
57                         AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED),
58                 mergedInlinePresentation(inlineFillUiInfo.mInlineRequest, 0, inlineAuthentication,
59                         ignoreHostSpec),
60                 createInlineSuggestionTooltip(inlineFillUiInfo.mInlineRequest,
61                         inlineFillUiInfo, InlineSuggestionInfo.SOURCE_AUTOFILL,
62                         response.getInlineTooltipPresentation()),
63                 uiCallback);
64     }
65 
66     /**
67      * Creates an array of {@link InlineSuggestion}s with the {@code datasets} provided by either
68      * regular/augmented autofill services.
69      */
70     @Nullable
createInlineSuggestions( @onNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo, @NonNull @InlineSuggestionInfo.Source String suggestionSource, @NonNull List<Dataset> datasets, @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback, boolean ignoreHostSpec)71     public static SparseArray<Pair<Dataset, InlineSuggestion>> createInlineSuggestions(
72             @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo,
73             @NonNull @InlineSuggestionInfo.Source String suggestionSource,
74             @NonNull List<Dataset> datasets,
75             @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback, boolean ignoreHostSpec) {
76         if (sDebug) Slog.d(TAG, "createInlineSuggestions(source=" + suggestionSource + ") called");
77 
78         final InlineSuggestionsRequest request = inlineFillUiInfo.mInlineRequest;
79         SparseArray<Pair<Dataset, InlineSuggestion>> response = new SparseArray<>(datasets.size());
80 
81         boolean hasTooltip = false;
82         for (int datasetIndex = 0; datasetIndex < datasets.size(); datasetIndex++) {
83             final Dataset dataset = datasets.get(datasetIndex);
84             final int fieldIndex = dataset.getFieldIds().indexOf(inlineFillUiInfo.mFocusedId);
85             if (fieldIndex < 0) {
86                 Slog.w(TAG, "AutofillId=" + inlineFillUiInfo.mFocusedId + " not found in dataset");
87                 continue;
88             }
89 
90             final InlinePresentation inlinePresentation =
91                     dataset.getFieldInlinePresentation(fieldIndex);
92             if (inlinePresentation == null) {
93                 Slog.w(TAG, "InlinePresentation not found in dataset");
94                 continue;
95             }
96 
97             final String suggestionType =
98                     dataset.getAuthentication() == null ? TYPE_SUGGESTION
99                             : InlineSuggestionInfo.TYPE_ACTION;
100             final int index = datasetIndex;
101 
102             InlineSuggestion inlineSuggestionTooltip = null;
103             if (!hasTooltip) {
104                 // Only available for first one inline suggestion tooltip.
105                 inlineSuggestionTooltip = createInlineSuggestionTooltip(request,
106                         inlineFillUiInfo, suggestionSource,
107                         dataset.getFieldInlineTooltipPresentation(fieldIndex));
108                 if (inlineSuggestionTooltip != null) {
109                     hasTooltip = true;
110                 }
111             }
112             InlineSuggestion inlineSuggestion = createInlineSuggestion(
113                     inlineFillUiInfo, suggestionSource, suggestionType,
114                     () -> uiCallback.autofill(dataset, index),
115                     mergedInlinePresentation(request, datasetIndex, inlinePresentation,
116                             ignoreHostSpec),
117                     inlineSuggestionTooltip,
118                     uiCallback);
119             response.append(datasetIndex, Pair.create(dataset, inlineSuggestion));
120         }
121 
122         return response;
123     }
124 
createInlineSuggestion( @onNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo, @NonNull @InlineSuggestionInfo.Source String suggestionSource, @NonNull @InlineSuggestionInfo.Type String suggestionType, @NonNull Runnable onClickAction, @NonNull InlinePresentation inlinePresentation, @Nullable InlineSuggestion tooltip, @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback)125     private static InlineSuggestion createInlineSuggestion(
126             @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo,
127             @NonNull @InlineSuggestionInfo.Source String suggestionSource,
128             @NonNull @InlineSuggestionInfo.Type String suggestionType,
129             @NonNull Runnable onClickAction,
130             @NonNull InlinePresentation inlinePresentation,
131             @Nullable InlineSuggestion tooltip,
132             @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) {
133 
134         final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
135                 inlinePresentation.getInlinePresentationSpec(), suggestionSource,
136                 inlinePresentation.getAutofillHints(), suggestionType,
137                 inlinePresentation.isPinned(), tooltip);
138 
139         return new InlineSuggestion(inlineSuggestionInfo,
140                 createInlineContentProvider(inlineFillUiInfo, inlinePresentation,
141                         onClickAction, uiCallback));
142     }
143 
144     /**
145      * Returns an {@link InlinePresentation} with the style spec from the request/host, and
146      * everything else from the provided {@code inlinePresentation}.
147      */
mergedInlinePresentation( @onNull InlineSuggestionsRequest request, int index, @NonNull InlinePresentation inlinePresentation, boolean ignoreHostSpec)148     private static InlinePresentation mergedInlinePresentation(
149             @NonNull InlineSuggestionsRequest request,
150             int index, @NonNull InlinePresentation inlinePresentation, boolean ignoreHostSpec) {
151         final List<InlinePresentationSpec> specs = request.getInlinePresentationSpecs();
152         if (specs.isEmpty()) {
153             return inlinePresentation;
154         }
155         InlinePresentationSpec specFromHost = specs.get(Math.min(specs.size() - 1, index));
156         InlinePresentationSpec specToUse =
157                 ignoreHostSpec ? inlinePresentation.getInlinePresentationSpec() : specFromHost;
158         InlinePresentationSpec mergedInlinePresentation = new InlinePresentationSpec.Builder(
159                 inlinePresentation.getInlinePresentationSpec().getMinSize(),
160                 inlinePresentation.getInlinePresentationSpec().getMaxSize()).setStyle(
161                 specToUse.getStyle()).build();
162 
163         return new InlinePresentation(inlinePresentation.getSlice(), mergedInlinePresentation,
164                 inlinePresentation.isPinned());
165     }
166 
167     // TODO(182306770): creates new class instead of the InlineSuggestion.
createInlineSuggestionTooltip( @onNull InlineSuggestionsRequest request, @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo, String suggestionSource, @NonNull InlinePresentation tooltipPresentation)168     private static InlineSuggestion createInlineSuggestionTooltip(
169             @NonNull InlineSuggestionsRequest request,
170             @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo,
171             String suggestionSource,
172             @NonNull InlinePresentation tooltipPresentation) {
173         if (tooltipPresentation == null) {
174             return null;
175         }
176 
177         final InlinePresentationSpec spec = request.getInlineTooltipPresentationSpec();
178         InlinePresentationSpec mergedSpec;
179         if (spec == null) {
180             mergedSpec = tooltipPresentation.getInlinePresentationSpec();
181         } else {
182             mergedSpec = new InlinePresentationSpec.Builder(
183                     tooltipPresentation.getInlinePresentationSpec().getMinSize(),
184                     tooltipPresentation.getInlinePresentationSpec().getMaxSize()).setStyle(
185                     spec.getStyle()).build();
186         }
187 
188         InlineFillUi.InlineSuggestionUiCallback uiCallback =
189                 new InlineFillUi.InlineSuggestionUiCallback() {
190                     @Override
191                     public void autofill(Dataset dataset, int datasetIndex) {
192                         /* nothing */
193                     }
194 
195                     @Override
196                     public void authenticate(int requestId, int datasetIndex) {
197                         /* nothing */
198                     }
199 
200                     @Override
201                     public void startIntentSender(IntentSender intentSender) {
202                         /* nothing */
203                     }
204 
205                     @Override
206                     public void onError() {
207                         Slog.w(TAG, "An error happened on the tooltip");
208                     }
209 
210                     @Override
211                     public void onInflate() {
212                         /* nothing */
213                     }
214                 };
215 
216         InlinePresentation tooltipInline = new InlinePresentation(tooltipPresentation.getSlice(),
217                 mergedSpec, false);
218         IInlineContentProvider tooltipContentProvider = createInlineContentProvider(
219                 inlineFillUiInfo, tooltipInline, () -> { /* no operation */ }, uiCallback);
220         final InlineSuggestionInfo tooltipInlineSuggestionInfo = new InlineSuggestionInfo(
221                 mergedSpec, suggestionSource, /* autofillHints */ null, TYPE_SUGGESTION,
222                 /* pinned */ false, /* tooltip */ null);
223         return new InlineSuggestion(tooltipInlineSuggestionInfo, tooltipContentProvider);
224     }
225 
createInlineContentProvider( @onNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo, @NonNull InlinePresentation inlinePresentation, @Nullable Runnable onClickAction, @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback)226     private static IInlineContentProvider createInlineContentProvider(
227             @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo,
228             @NonNull InlinePresentation inlinePresentation, @Nullable Runnable onClickAction,
229             @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) {
230         RemoteInlineSuggestionViewConnector remoteInlineSuggestionViewConnector =
231                 new RemoteInlineSuggestionViewConnector(inlineFillUiInfo, inlinePresentation,
232                         onClickAction, uiCallback);
233 
234         return new InlineContentProviderImpl(remoteInlineSuggestionViewConnector, null);
235     }
236 
InlineSuggestionFactory()237     private InlineSuggestionFactory() {
238     }
239 }