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 }