diff options
9 files changed, 338 insertions, 94 deletions
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index 484eddc8c2f6..6334d9d765c8 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -38,6 +38,7 @@ import android.os.ICancellationSignal; import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; +import android.service.autofill.Dataset; import android.service.autofill.augmented.PresentationParams.SystemPopupPresentationParams; import android.util.Log; import android.util.Pair; @@ -47,6 +48,7 @@ import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; import android.view.autofill.IAugmentedAutofillManagerClient; import android.view.autofill.IAutofillWindowPresenter; +import android.view.inputmethod.InlineSuggestionsRequest; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -106,10 +108,11 @@ public abstract class AugmentedAutofillService extends Service { @Override public void onFillRequest(int sessionId, IBinder client, int taskId, ComponentName componentName, AutofillId focusedId, AutofillValue focusedValue, - long requestTime, IFillCallback callback) { + long requestTime, @Nullable InlineSuggestionsRequest inlineSuggestionsRequest, + IFillCallback callback) { mHandler.sendMessage(obtainMessage(AugmentedAutofillService::handleOnFillRequest, AugmentedAutofillService.this, sessionId, client, taskId, componentName, - focusedId, focusedValue, requestTime, callback)); + focusedId, focusedValue, requestTime, inlineSuggestionsRequest, callback)); } @Override @@ -212,6 +215,7 @@ public abstract class AugmentedAutofillService extends Service { private void handleOnFillRequest(int sessionId, @NonNull IBinder client, int taskId, @NonNull ComponentName componentName, @NonNull AutofillId focusedId, @Nullable AutofillValue focusedValue, long requestTime, + @Nullable InlineSuggestionsRequest inlineSuggestionsRequest, @NonNull IFillCallback callback) { if (mAutofillProxies == null) { mAutofillProxies = new SparseArray<>(); @@ -236,9 +240,8 @@ public abstract class AugmentedAutofillService extends Service { } catch (RemoteException e) { e.rethrowFromSystemServer(); } - // TODO(b/146453195): pass the inline suggestion request over. - onFillRequest(new FillRequest(proxy, /* inlineSuggestionsRequest= */null), - cancellationSignal, new FillController(proxy), new FillCallback(proxy)); + onFillRequest(new FillRequest(proxy, inlineSuggestionsRequest), cancellationSignal, + new FillController(proxy), new FillCallback(proxy)); } private void handleOnDestroyAllFillWindowsRequest() { @@ -484,6 +487,14 @@ public abstract class AugmentedAutofillService extends Service { } } + public void onInlineSuggestionsDataReady(@NonNull List<Dataset> inlineSuggestionsData) { + try { + mCallback.onSuccess(inlineSuggestionsData.toArray(new Dataset[]{})); + } catch (RemoteException e) { + Log.e(TAG, "Error calling back with the inline suggestions data: " + e); + } + } + // Used (mostly) for metrics. public void report(@ReportEvent int event) { if (sVerbose) Log.v(TAG, "report(): " + event); diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java index 0251386bd7ce..b767799847d7 100644 --- a/core/java/android/service/autofill/augmented/FillCallback.java +++ b/core/java/android/service/autofill/augmented/FillCallback.java @@ -21,9 +21,12 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.service.autofill.Dataset; import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy; import android.util.Log; +import java.util.List; + /** * Callback used to indicate at {@link FillRequest} has been fulfilled. * @@ -45,7 +48,7 @@ public final class FillCallback { * Sets the response associated with the request. * * @param response response associated with the request, or {@code null} if the service - * could not provide autofill for the request. + * could not provide autofill for the request. */ public void onSuccess(@Nullable FillResponse response) { if (sDebug) Log.d(TAG, "onSuccess(): " + response); @@ -55,6 +58,12 @@ public final class FillCallback { return; } + List<Dataset> inlineSuggestions = response.getInlineSuggestions(); + if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) { + mProxy.onInlineSuggestionsDataReady(inlineSuggestions); + return; + } + final FillWindow fillWindow = response.getFillWindow(); if (fillWindow != null) { fillWindow.show(); diff --git a/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl b/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl index 103fc4d7dfcf..4911348c692f 100644 --- a/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl +++ b/core/java/android/service/autofill/augmented/IAugmentedAutofillService.aidl @@ -22,6 +22,7 @@ import android.os.IBinder; import android.service.autofill.augmented.IFillCallback; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; +import android.view.inputmethod.InlineSuggestionsRequest; import java.util.List; @@ -35,7 +36,9 @@ oneway interface IAugmentedAutofillService { void onDisconnected(); void onFillRequest(int sessionId, in IBinder autofillManagerClient, int taskId, in ComponentName activityComponent, in AutofillId focusedId, - in AutofillValue focusedValue, long requestTime, in IFillCallback callback); + in AutofillValue focusedValue, long requestTime, + in InlineSuggestionsRequest inlineSuggestionsRequest, + in IFillCallback callback); void onDestroyAllFillWindowsRequest(); } diff --git a/core/java/com/android/internal/infra/ServiceConnector.java b/core/java/com/android/internal/infra/ServiceConnector.java index 857377afd3e8..e9d7d05fe68d 100644 --- a/core/java/com/android/internal/infra/ServiceConnector.java +++ b/core/java/com/android/internal/infra/ServiceConnector.java @@ -223,7 +223,7 @@ public interface ServiceConnector<I extends IInterface> { private final @NonNull ServiceConnection mServiceConnection = this; private final @NonNull Runnable mTimeoutDisconnect = this; - private final @NonNull Context mContext; + protected final @NonNull Context mContext; private final @NonNull Intent mIntent; private final int mBindingFlags; private final int mUserId; diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java new file mode 100644 index 000000000000..3af15c7a0e5c --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.autofill; + +import static com.android.server.autofill.Helper.sDebug; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.Handler; +import android.os.RemoteException; +import android.service.autofill.Dataset; +import android.util.Slog; +import android.view.SurfaceControl; +import android.view.View; +import android.view.autofill.AutofillId; +import android.view.autofill.IAutoFillManagerClient; +import android.view.inline.InlinePresentationSpec; +import android.view.inputmethod.InlineSuggestion; +import android.view.inputmethod.InlineSuggestionInfo; +import android.view.inputmethod.InlineSuggestionsRequest; +import android.view.inputmethod.InlineSuggestionsResponse; + +import com.android.internal.view.inline.IInlineContentCallback; +import com.android.internal.view.inline.IInlineContentProvider; +import com.android.server.autofill.ui.InlineSuggestionUi; + +import java.util.ArrayList; + + +/** + * @hide + */ +public final class InlineSuggestionFactory { + private static final String TAG = "InlineSuggestionFactory"; + + /** + * Creates an {@link InlineSuggestionsResponse} with the {@code datasets} provided by + * augmented autofill service. + */ + public static InlineSuggestionsResponse createAugmentedInlineSuggestionsResponse( + int sessionId, + @NonNull Dataset[] datasets, + @NonNull AutofillId autofillId, + @NonNull InlineSuggestionsRequest request, + @NonNull Handler uiHandler, + @NonNull Context context, + @NonNull IAutoFillManagerClient client) { + if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called"); + + final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>(); + final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context); + for (Dataset dataset : datasets) { + // TODO(b/146453195): use the spec in the dataset. + InlinePresentationSpec spec = request.getPresentationSpecs().get(0); + if (spec == null) { + Slog.w(TAG, "InlinePresentationSpec is not provided in the response data set"); + continue; + } + InlineSuggestion inlineSuggestion = createAugmentedInlineSuggestion(sessionId, dataset, + autofillId, spec, uiHandler, inlineSuggestionUi, client); + inlineSuggestions.add(inlineSuggestion); + } + return new InlineSuggestionsResponse(inlineSuggestions); + } + + private static InlineSuggestion createAugmentedInlineSuggestion(int sessionId, + @NonNull Dataset dataset, + @NonNull AutofillId autofillId, + @NonNull InlinePresentationSpec spec, + @NonNull Handler uiHandler, + @NonNull InlineSuggestionUi inlineSuggestionUi, + @NonNull IAutoFillManagerClient client) { + // TODO(b/146453195): fill in the autofill hint properly. + final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo( + spec, InlineSuggestionInfo.SOURCE_PLATFORM, new String[]{""}); + final View.OnClickListener onClickListener = createOnClickListener(sessionId, dataset, + client); + final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo, + createInlineContentProvider(autofillId, dataset, uiHandler, inlineSuggestionUi, + onClickListener)); + return inlineSuggestion; + } + + private static IInlineContentProvider.Stub createInlineContentProvider( + @NonNull AutofillId autofillId, @NonNull Dataset dataset, @NonNull Handler uiHandler, + @NonNull InlineSuggestionUi inlineSuggestionUi, + @Nullable View.OnClickListener onClickListener) { + return new IInlineContentProvider.Stub() { + @Override + public void provideContent(int width, int height, + IInlineContentCallback callback) { + uiHandler.post(() -> { + SurfaceControl sc = inlineSuggestionUi.inflate(dataset, autofillId, + width, height, onClickListener); + try { + callback.onContent(sc); + } catch (RemoteException e) { + Slog.w(TAG, "Encounter exception calling back with inline content."); + } + }); + } + }; + } + + private static View.OnClickListener createOnClickListener(int sessionId, + @NonNull Dataset dataset, + @NonNull IAutoFillManagerClient client) { + return v -> { + if (sDebug) Slog.d(TAG, "Inline suggestion clicked"); + try { + client.autofill(sessionId, dataset.getFieldIds(), dataset.getFieldValues()); + } catch (RemoteException e) { + Slog.w(TAG, "Encounter exception autofilling the values"); + } + }; + } + + private InlineSuggestionFactory() { + } +} diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index c5011b343b88..2a7357b8fca6 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -46,12 +46,15 @@ import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; import android.view.autofill.IAutoFillManagerClient; +import android.view.inputmethod.InlineSuggestionsRequest; import com.android.internal.infra.AbstractRemoteService; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.IResultReceiver; +import com.android.internal.util.ArrayUtils; +import com.android.internal.view.IInlineSuggestionsResponseCallback; import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; @@ -137,7 +140,9 @@ final class RemoteAugmentedAutofillService */ public void onRequestAutofillLocked(int sessionId, @NonNull IAutoFillManagerClient client, int taskId, @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId, - @Nullable AutofillValue focusedValue) { + @Nullable AutofillValue focusedValue, + @Nullable InlineSuggestionsRequest inlineSuggestionsRequest, + @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback) { long requestTime = SystemClock.elapsedRealtime(); AtomicReference<ICancellationSignal> cancellationRef = new AtomicReference<>(); @@ -151,10 +156,13 @@ final class RemoteAugmentedAutofillService final IBinder realClient = resultData .getBinder(AutofillManager.EXTRA_AUGMENTED_AUTOFILL_CLIENT); service.onFillRequest(sessionId, realClient, taskId, activityComponent, - focusedId, focusedValue, requestTime, new IFillCallback.Stub() { + focusedId, focusedValue, requestTime, inlineSuggestionsRequest, + new IFillCallback.Stub() { @Override public void onSuccess(@Nullable Dataset[] inlineSuggestionsData) { - // TODO(b/146453195): handle non-null inline suggestions data. + maybeHandleInlineSuggestions(sessionId, inlineSuggestionsData, + focusedId, inlineSuggestionsRequest, + inlineSuggestionsCallback, client); requestAutofill.complete(null); } @@ -216,6 +224,26 @@ final class RemoteAugmentedAutofillService }); } + private void maybeHandleInlineSuggestions(int sessionId, + @Nullable Dataset[] inlineSuggestionsData, @NonNull AutofillId focusedId, + @Nullable InlineSuggestionsRequest inlineSuggestionsRequest, + @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback, + @NonNull IAutoFillManagerClient client) { + if (inlineSuggestionsRequest == null + || ArrayUtils.isEmpty(inlineSuggestionsData) + || inlineSuggestionsCallback == null) { + return; + } + try { + inlineSuggestionsCallback.onInlineSuggestionsResponse( + InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse( + sessionId, inlineSuggestionsData, focusedId, inlineSuggestionsRequest, + getJobHandler(), mContext, client)); + } catch (RemoteException e) { + Slog.w(TAG, "Exception sending inline suggestions response back to IME."); + } + } + @Override public String toString() { return "RemoteAugmentedAutofillService[" diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 5af43994775a..864c96d7ef1f 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -424,19 +424,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList<FillContext> contexts = mergePreviousSessionLocked(/* forSave= */ false); - InlineSuggestionsRequest suggestionsRequest = null; - if (mSuggestionsRequestFuture != null) { - try { - suggestionsRequest = mSuggestionsRequestFuture.get( - INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions request in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions request cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } + final InlineSuggestionsRequest suggestionsRequest = getInlineSuggestionsRequest(); request = new FillRequest(requestId, contexts, mClientState, flags, suggestionsRequest); @@ -2692,21 +2680,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ private boolean requestShowInlineSuggestions(List<Slice> inlineSuggestionSlices, FillResponse response) { - IInlineSuggestionsResponseCallback inlineContentCallback = null; - synchronized (mLock) { - if (mInlineSuggestionsResponseCallbackFuture != null) { - try { - inlineContentCallback = mInlineSuggestionsResponseCallbackFuture.get( - INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions callback in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions callback cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - } + final IInlineSuggestionsResponseCallback inlineContentCallback = + getInlineSuggestionsResponseCallback(); if (inlineContentCallback == null) { Log.w(TAG, "Session input method callback is not set yet"); @@ -3063,9 +3038,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId); - // TODO(b/137800469): implement inlined suggestions for augmented autofill + final InlineSuggestionsRequest inlineSuggestionsRequest = getInlineSuggestionsRequest(); + final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback = + getInlineSuggestionsResponseCallback(); + remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId, - currentValue); + currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback); if (mAugmentedAutofillDestroyer == null) { mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest(); @@ -3073,6 +3051,40 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return mAugmentedAutofillDestroyer; } + @Nullable + private InlineSuggestionsRequest getInlineSuggestionsRequest() { + if (mSuggestionsRequestFuture != null) { + try { + return mSuggestionsRequestFuture.get( + INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + Log.w(TAG, "Exception getting inline suggestions request in time: " + e); + } catch (CancellationException e) { + Log.w(TAG, "Inline suggestions request cancelled"); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + return null; + } + + @Nullable + private IInlineSuggestionsResponseCallback getInlineSuggestionsResponseCallback() { + if (mInlineSuggestionsResponseCallbackFuture != null) { + try { + return mInlineSuggestionsResponseCallbackFuture.get( + INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + Log.w(TAG, "Exception getting inline suggestions callback in time: " + e); + } catch (CancellationException e) { + Log.w(TAG, "Inline suggestions callback cancelled"); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + return null; + } + @GuardedBy("mLock") private void cancelAugmentedAutofillLocked() { final RemoteAugmentedAutofillService remoteService = mService diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index e57b7b3d6d32..0511bf2f3b74 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -24,8 +24,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; -import android.graphics.Color; -import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.metrics.LogMaker; import android.os.Bundle; @@ -40,13 +38,9 @@ import android.text.TextUtils; import android.util.Slog; import android.view.KeyEvent; import android.view.SurfaceControl; -import android.view.SurfaceControlViewHost; -import android.view.WindowManager; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; -import android.view.autofill.AutofillValue; import android.view.autofill.IAutofillWindowPresenter; -import android.widget.TextView; import android.widget.Toast; import com.android.internal.logging.MetricsLogger; @@ -189,8 +183,16 @@ public final class AutoFillUI { Slog.w(TAG, "getSuggestionSurfaceForShowing() called with null dataset"); } mHandler.post(() -> { - final SurfaceControl suggestionSurface = inflateInlineSuggestion(dataset, response, - autofillId, width, height); + final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(mContext); + final SurfaceControl suggestionSurface = inlineSuggestionUi.inflate(dataset, + autofillId, width, height, v -> { + Slog.d(TAG, "Inline suggestion clicked"); + hideFillUiUiThread(mCallback, true); + if (mCallback != null) { + final int datasetIndex = response.getDatasets().indexOf(dataset); + mCallback.fill(response.getRequestId(), datasetIndex, dataset); + } + }); try { cb.onContent(suggestionSurface); @@ -201,51 +203,6 @@ public final class AutoFillUI { } /** - * TODO(b/137800469): Fill in javadoc, generate custom templated view for inline suggestions. - * TODO: Move to ExtServices. - * - * @return a {@link SurfaceControl} with the inflated content embedded in it. - */ - private SurfaceControl inflateInlineSuggestion(@NonNull Dataset dataset, - @NonNull FillResponse response, AutofillId autofillId, int width, int height) { - Slog.i(TAG, "inflate() called"); - final Context context = mContext; - final int index = dataset.getFieldIds().indexOf(autofillId); - if (index < 0) { - Slog.w(TAG, "inflateInlineSuggestion(): AutofillId=" + autofillId - + " not found in dataset"); - } - - final AutofillValue datasetValue = dataset.getFieldValues().get(index); - //TODO(b/137800469): Pass in inputToken from IME. - final SurfaceControlViewHost wvr = new SurfaceControlViewHost(context, context.getDisplay(), - (IBinder) null); - // TODO(b/134365580): Use the package instead of the SurfaceControl itself - // for accessibility support. - final SurfaceControl sc = wvr.getSurfacePackage().getSurfaceControl(); - - TextView textView = new TextView(context); - textView.setText(datasetValue.getTextValue()); - textView.setBackgroundColor(Color.WHITE); - textView.setTextColor(Color.BLACK); - textView.setOnClickListener(v -> { - Slog.d(TAG, "Inline suggestion clicked"); - hideFillUiUiThread(mCallback, true); - if (mCallback != null) { - final int datasetIndex = response.getDatasets().indexOf(dataset); - mCallback.fill(response.getRequestId(), datasetIndex, dataset); - } - }); - - WindowManager.LayoutParams lp = - new WindowManager.LayoutParams(width, height, - WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE); - wvr.addView(textView, lp); - - return sc; - } - - /** * Shows the fill UI, removing the previous fill UI if the has changed. * * @param focusedId the currently focused field diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java new file mode 100644 index 000000000000..17cb7391b25a --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.autofill.ui; + +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.os.IBinder; +import android.service.autofill.Dataset; +import android.util.Log; +import android.util.Slog; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.View; +import android.view.WindowManager; +import android.view.autofill.AutofillId; +import android.view.autofill.AutofillValue; +import android.widget.TextView; + +/** + * This is a temporary inline suggestion UI inflater which will be replaced by the ExtServices + * implementation. + * + * TODO(b/146453086): remove this class once autofill ext service is implemented. + * + * @hide + */ +public class InlineSuggestionUi { + + private static final String TAG = "InlineSuggestionUi"; + + private final Context mContext; + + public InlineSuggestionUi(Context context) { + this.mContext = context; + } + + /** + * Returns a {@link SurfaceControl} with the inflated content embedded in it. + */ + @MainThread + @Nullable + public SurfaceControl inflate(@NonNull Dataset dataset, @NonNull AutofillId autofillId, + int width, int height, @Nullable View.OnClickListener onClickListener) { + Log.d(TAG, "Inflating the inline suggestion UI"); + final int index = dataset.getFieldIds().indexOf(autofillId); + if (index < 0) { + Slog.w(TAG, "inflateInlineSuggestion(): AutofillId=" + autofillId + + " not found in dataset"); + return null; + } + final AutofillValue datasetValue = dataset.getFieldValues().get(index); + //TODO(b/137800469): Pass in inputToken from IME. + final SurfaceControlViewHost wvr = new SurfaceControlViewHost(mContext, + mContext.getDisplay(), (IBinder) null); + final SurfaceControl sc = wvr.getSurfacePackage().getSurfaceControl(); + + TextView textView = new TextView(mContext); + textView.setText(datasetValue.getTextValue()); + textView.setBackgroundColor(Color.WHITE); + textView.setTextColor(Color.BLACK); + if (onClickListener != null) { + textView.setOnClickListener(onClickListener); + } + + WindowManager.LayoutParams lp = + new WindowManager.LayoutParams(width, height, + WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT); + wvr.addView(textView, lp); + return sc; + } +} |