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 }