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