diff --git a/core/api/current.txt b/core/api/current.txt index c21a44f116f88..f23483c262075 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -37401,12 +37401,15 @@ package android.service.autofill { method @NonNull public android.service.autofill.Dataset.Builder setAuthentication(@Nullable android.content.IntentSender); method @NonNull public android.service.autofill.Dataset.Builder setId(@Nullable String); method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation); method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation); } public final class DateTransformation implements android.os.Parcelable android.service.autofill.Transformation { @@ -37507,6 +37510,7 @@ package android.service.autofill { method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long); method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews); method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation, @Nullable android.service.autofill.InlinePresentation); method @NonNull public android.service.autofill.FillResponse.Builder setClientState(@Nullable android.os.Bundle); method @NonNull public android.service.autofill.FillResponse.Builder setFieldClassificationIds(@NonNull android.view.autofill.AutofillId...); method @NonNull public android.service.autofill.FillResponse.Builder setFlags(int); @@ -37534,6 +37538,7 @@ package android.service.autofill { public final class InlinePresentation implements android.os.Parcelable { ctor public InlinePresentation(@NonNull android.app.slice.Slice, @NonNull android.widget.inline.InlinePresentationSpec, boolean); + method @NonNull public static android.service.autofill.InlinePresentation createTooltipPresentation(@NonNull android.app.slice.Slice, @NonNull android.widget.inline.InlinePresentationSpec); method public int describeContents(); method @NonNull public android.widget.inline.InlinePresentationSpec getInlinePresentationSpec(); method @NonNull public android.app.slice.Slice getSlice(); @@ -51336,6 +51341,7 @@ package android.view.inputmethod { method @NonNull public android.os.Bundle getExtras(); method @NonNull public String getHostPackageName(); method @NonNull public java.util.List getInlinePresentationSpecs(); + method @Nullable public android.widget.inline.InlinePresentationSpec getInlineTooltipPresentationSpec(); method public int getMaxSuggestionCount(); method @NonNull public android.os.LocaleList getSupportedLocales(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -51347,9 +51353,12 @@ package android.view.inputmethod { ctor public InlineSuggestionsRequest.Builder(@NonNull java.util.List); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder addInlinePresentationSpecs(@NonNull android.widget.inline.InlinePresentationSpec); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest build(); + method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@NonNull android.os.Bundle); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(@NonNull java.util.List); + method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlineTooltipPresentationSpec(@NonNull android.widget.inline.InlinePresentationSpec); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setMaxSuggestionCount(int); + method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setSupportedLocales(@NonNull android.os.LocaleList); } diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index e3d0741b46039..6147c58867a66 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -107,10 +107,12 @@ public final class Dataset implements Parcelable { private final ArrayList mFieldValues; private final ArrayList mFieldPresentations; private final ArrayList mFieldInlinePresentations; + private final ArrayList mFieldInlineTooltipPresentations; private final ArrayList mFieldFilters; @Nullable private final ClipData mFieldContent; private final RemoteViews mPresentation; @Nullable private final InlinePresentation mInlinePresentation; + @Nullable private final InlinePresentation mInlineTooltipPresentation; private final IntentSender mAuthentication; @Nullable String mId; @@ -119,10 +121,12 @@ public final class Dataset implements Parcelable { mFieldValues = builder.mFieldValues; mFieldPresentations = builder.mFieldPresentations; mFieldInlinePresentations = builder.mFieldInlinePresentations; + mFieldInlineTooltipPresentations = builder.mFieldInlineTooltipPresentations; mFieldFilters = builder.mFieldFilters; mFieldContent = builder.mFieldContent; mPresentation = builder.mPresentation; mInlinePresentation = builder.mInlinePresentation; + mInlineTooltipPresentation = builder.mInlineTooltipPresentation; mAuthentication = builder.mAuthentication; mId = builder.mId; } @@ -153,6 +157,14 @@ public final class Dataset implements Parcelable { return inlinePresentation != null ? inlinePresentation : mInlinePresentation; } + /** @hide */ + public @Nullable InlinePresentation getFieldInlineTooltipPresentation(int index) { + final InlinePresentation inlineTooltipPresentation = + mFieldInlineTooltipPresentations.get(index); + return inlineTooltipPresentation != null + ? inlineTooltipPresentation : mInlineTooltipPresentation; + } + /** @hide */ public @Nullable DatasetFieldFilter getFilter(int index) { return mFieldFilters.get(index); @@ -209,6 +221,10 @@ public final class Dataset implements Parcelable { if (mFieldInlinePresentations != null) { builder.append(", fieldInlinePresentations=").append(mFieldInlinePresentations.size()); } + if (mFieldInlineTooltipPresentations != null) { + builder.append(", fieldInlineTooltipInlinePresentations=").append( + mFieldInlineTooltipPresentations.size()); + } if (mFieldFilters != null) { builder.append(", fieldFilters=").append(mFieldFilters.size()); } @@ -218,6 +234,9 @@ public final class Dataset implements Parcelable { if (mInlinePresentation != null) { builder.append(", hasInlinePresentation"); } + if (mInlineTooltipPresentation != null) { + builder.append(", hasInlineTooltipPresentation"); + } if (mAuthentication != null) { builder.append(", hasAuthentication"); } @@ -245,10 +264,12 @@ public final class Dataset implements Parcelable { private ArrayList mFieldValues; private ArrayList mFieldPresentations; private ArrayList mFieldInlinePresentations; + private ArrayList mFieldInlineTooltipPresentations; private ArrayList mFieldFilters; @Nullable private ClipData mFieldContent; private RemoteViews mPresentation; @Nullable private InlinePresentation mInlinePresentation; + @Nullable private InlinePresentation mInlineTooltipPresentation; private IntentSender mAuthentication; private boolean mDestroyed; @Nullable private String mId; @@ -305,6 +326,31 @@ public final class Dataset implements Parcelable { return this; } + /** + * Visualizes this dataset as inline suggestions. + * + * @param inlinePresentation the {@link InlinePresentation} used to visualize this + * dataset as inline suggestions. If the dataset supports inline suggestions this + * should not be null. + * @param inlineTooltipPresentation the {@link InlinePresentation} used to show + * the tooltip for the {@code inlinePresentation}. + * + * @throws IllegalStateException if {@link #build()} was already called. + * + * @return this builder. + */ + public @NonNull Builder setInlinePresentation( + @NonNull InlinePresentation inlinePresentation, + @NonNull InlinePresentation inlineTooltipPresentation) { + throwIfDestroyed(); + Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null"); + Preconditions.checkNotNull(inlineTooltipPresentation, + "inlineTooltipPresentation must be non-null"); + mInlinePresentation = inlinePresentation; + mInlineTooltipPresentation = inlineTooltipPresentation; + return this; + } + /** * Triggers a custom UI before before autofilling the screen with the contents of this * dataset. @@ -596,6 +642,41 @@ public final class Dataset implements Parcelable { return this; } + /** + * Sets the value of a field, using a custom {@link RemoteViews presentation} to + * visualize it and an {@link InlinePresentation} to visualize it as an inline suggestion. + * + * @see #setValue(AutofillId, AutofillValue, RemoteViews, InlinePresentation) + * + * @param id id returned by {@link + * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. + * @param value the value to be autofilled. Pass {@code null} if you do not have the value + * but the target view is a logical part of the dataset. For example, if + * the dataset needs authentication and you have no access to the value. + * @param presentation the presentation used to visualize this field. + * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset + * as inline suggestions. If the dataset supports inline suggestions, + * this should not be null. + * @param inlineTooltipPresentation The {@link InlinePresentation} used to show + * the tooltip for the {@code inlinePresentation}. + * + * @throws IllegalStateException if {@link #build()} was already called. + * + * @return this builder. + */ + public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, + @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation, + @NonNull InlinePresentation inlineTooltipPresentation) { + throwIfDestroyed(); + Preconditions.checkNotNull(presentation, "presentation cannot be null"); + Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null"); + Preconditions.checkNotNull(inlineTooltipPresentation, + "inlineTooltipPresentation cannot be null"); + setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, + inlineTooltipPresentation, null); + return this; + } + /** * Sets the value of a field, using a custom {@link RemoteViews presentation} to * visualize it and a explicit filter, and an @@ -640,6 +721,47 @@ public final class Dataset implements Parcelable { return this; } + /** + * Sets the value of a field, using a custom {@link RemoteViews presentation} to + * visualize it and a explicit filter, and an + * {@link InlinePresentation} to visualize it as an inline suggestion. + * + * @see #setValue(AutofillId, AutofillValue, Pattern, RemoteViews, InlinePresentation) + * + * @param id id returned by {@link + * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. + * @param value the value to be autofilled. Pass {@code null} if you do not have the value + * but the target view is a logical part of the dataset. For example, if + * the dataset needs authentication and you have no access to the value. + * @param filter regex used to determine if the dataset should be shown in the autofill UI; + * when {@code null}, it disables filtering on that dataset (this is the recommended + * approach when {@code value} is not {@code null} and field contains sensitive data + * such as passwords). + * @param presentation the presentation used to visualize this field. + * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset + * as inline suggestions. If the dataset supports inline suggestions, this + * should not be null. + * @param inlineTooltipPresentation The {@link InlinePresentation} used to show + * the tooltip for the {@code inlinePresentation}. + * + * @throws IllegalStateException if {@link #build()} was already called. + * + * @return this builder. + */ + public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, + @Nullable Pattern filter, @NonNull RemoteViews presentation, + @NonNull InlinePresentation inlinePresentation, + @NonNull InlinePresentation inlineTooltipPresentation) { + throwIfDestroyed(); + Preconditions.checkNotNull(presentation, "presentation cannot be null"); + Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null"); + Preconditions.checkNotNull(inlineTooltipPresentation, + "inlineTooltipPresentation cannot be null"); + setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, + inlineTooltipPresentation, new DatasetFieldFilter(filter)); + return this; + } + /** * Sets the value of a field with an explicit filter, and using an * {@link InlinePresentation} to visualize it as an inline suggestion. @@ -680,6 +802,15 @@ public final class Dataset implements Parcelable { @Nullable AutofillValue value, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation, @Nullable DatasetFieldFilter filter) { + setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null, + filter); + } + + private void setLifeTheUniverseAndEverything(@NonNull AutofillId id, + @Nullable AutofillValue value, @Nullable RemoteViews presentation, + @Nullable InlinePresentation inlinePresentation, + @Nullable InlinePresentation tooltip, + @Nullable DatasetFieldFilter filter) { Preconditions.checkNotNull(id, "id cannot be null"); if (mFieldIds != null) { final int existingIdx = mFieldIds.indexOf(id); @@ -687,6 +818,7 @@ public final class Dataset implements Parcelable { mFieldValues.set(existingIdx, value); mFieldPresentations.set(existingIdx, presentation); mFieldInlinePresentations.set(existingIdx, inlinePresentation); + mFieldInlineTooltipPresentations.set(existingIdx, tooltip); mFieldFilters.set(existingIdx, filter); return; } @@ -695,12 +827,14 @@ public final class Dataset implements Parcelable { mFieldValues = new ArrayList<>(); mFieldPresentations = new ArrayList<>(); mFieldInlinePresentations = new ArrayList<>(); + mFieldInlineTooltipPresentations = new ArrayList<>(); mFieldFilters = new ArrayList<>(); } mFieldIds.add(id); mFieldValues.add(value); mFieldPresentations.add(presentation); mFieldInlinePresentations.add(inlinePresentation); + mFieldInlineTooltipPresentations.add(tooltip); mFieldFilters.add(filter); } @@ -755,10 +889,12 @@ public final class Dataset implements Parcelable { public void writeToParcel(Parcel parcel, int flags) { parcel.writeParcelable(mPresentation, flags); parcel.writeParcelable(mInlinePresentation, flags); + parcel.writeParcelable(mInlineTooltipPresentation, flags); parcel.writeTypedList(mFieldIds, flags); parcel.writeTypedList(mFieldValues, flags); parcel.writeTypedList(mFieldPresentations, flags); parcel.writeTypedList(mFieldInlinePresentations, flags); + parcel.writeTypedList(mFieldInlineTooltipPresentations, flags); parcel.writeTypedList(mFieldFilters, flags); parcel.writeParcelable(mFieldContent, flags); parcel.writeParcelable(mAuthentication, flags); @@ -770,6 +906,8 @@ public final class Dataset implements Parcelable { public Dataset createFromParcel(Parcel parcel) { final RemoteViews presentation = parcel.readParcelable(null); final InlinePresentation inlinePresentation = parcel.readParcelable(null); + final InlinePresentation inlineTooltipPresentation = + parcel.readParcelable(null); final ArrayList ids = parcel.createTypedArrayList(AutofillId.CREATOR); final ArrayList values = @@ -778,6 +916,8 @@ public final class Dataset implements Parcelable { parcel.createTypedArrayList(RemoteViews.CREATOR); final ArrayList inlinePresentations = parcel.createTypedArrayList(InlinePresentation.CREATOR); + final ArrayList inlineTooltipPresentations = + parcel.createTypedArrayList(InlinePresentation.CREATOR); final ArrayList filters = parcel.createTypedArrayList(DatasetFieldFilter.CREATOR); final ClipData fieldContent = parcel.readParcelable(null); @@ -790,8 +930,13 @@ public final class Dataset implements Parcelable { final Builder builder = (presentation != null) ? new Builder(presentation) : new Builder(); if (inlinePresentation != null) { - builder.setInlinePresentation(inlinePresentation); + if (inlineTooltipPresentation != null) { + builder.setInlinePresentation(inlinePresentation, inlineTooltipPresentation); + } else { + builder.setInlinePresentation(inlinePresentation); + } } + if (fieldContent != null) { builder.setContent(ids.get(0), fieldContent); } @@ -802,9 +947,11 @@ public final class Dataset implements Parcelable { final RemoteViews fieldPresentation = presentations.get(i); final InlinePresentation fieldInlinePresentation = i < inlinePresentationsSize ? inlinePresentations.get(i) : null; + final InlinePresentation fieldInlineTooltipPresentation = + i < inlinePresentationsSize ? inlineTooltipPresentations.get(i) : null; final DatasetFieldFilter filter = filters.get(i); builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, - fieldInlinePresentation, filter); + fieldInlinePresentation, fieldInlineTooltipPresentation, filter); } builder.setAuthentication(authentication); builder.setId(datasetId); diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index b1107a8c2efb3..740ae260999ff 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -23,6 +23,7 @@ import static android.view.autofill.Helper.sDebug; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.Activity; import android.content.IntentSender; @@ -76,6 +77,7 @@ public final class FillResponse implements Parcelable { private final @Nullable Bundle mClientState; private final @Nullable RemoteViews mPresentation; private final @Nullable InlinePresentation mInlinePresentation; + private final @Nullable InlinePresentation mInlineTooltipPresentation; private final @Nullable RemoteViews mHeader; private final @Nullable RemoteViews mFooter; private final @Nullable IntentSender mAuthentication; @@ -95,6 +97,7 @@ public final class FillResponse implements Parcelable { mClientState = builder.mClientState; mPresentation = builder.mPresentation; mInlinePresentation = builder.mInlinePresentation; + mInlineTooltipPresentation = builder.mInlineTooltipPresentation; mHeader = builder.mHeader; mFooter = builder.mFooter; mAuthentication = builder.mAuthentication; @@ -134,6 +137,11 @@ public final class FillResponse implements Parcelable { return mInlinePresentation; } + /** @hide */ + public @Nullable InlinePresentation getInlineTooltipPresentation() { + return mInlineTooltipPresentation; + } + /** @hide */ public @Nullable RemoteViews getHeader() { return mHeader; @@ -219,6 +227,7 @@ public final class FillResponse implements Parcelable { private Bundle mClientState; private RemoteViews mPresentation; private InlinePresentation mInlinePresentation; + private InlinePresentation mInlineTooltipPresentation; private RemoteViews mHeader; private RemoteViews mFooter; private IntentSender mAuthentication; @@ -360,6 +369,22 @@ public final class FillResponse implements Parcelable { public Builder setAuthentication(@NonNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation) { + return setAuthentication(ids, authentication, presentation, inlinePresentation, null); + } + + /** + * Triggers a custom UI before before autofilling the screen with any data set in this + * response. + * + *

This method like + * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews, InlinePresentation)} + * but allows setting an {@link InlinePresentation} for the inline suggestion tooltip. + */ + @NonNull + public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids, + @Nullable IntentSender authentication, @Nullable RemoteViews presentation, + @Nullable InlinePresentation inlinePresentation, + @Nullable InlinePresentation inlineTooltipPresentation) { throwIfDestroyed(); throwIfDisableAutofillCalled(); if (mHeader != null || mFooter != null) { @@ -373,6 +398,7 @@ public final class FillResponse implements Parcelable { mAuthentication = authentication; mPresentation = presentation; mInlinePresentation = inlinePresentation; + mInlineTooltipPresentation = inlineTooltipPresentation; mAuthenticationIds = assertValid(ids); return this; } @@ -737,6 +763,9 @@ public final class FillResponse implements Parcelable { if (mInlinePresentation != null) { builder.append(", hasInlinePresentation"); } + if (mInlineTooltipPresentation != null) { + builder.append(", hasInlineTooltipPresentation"); + } if (mHeader != null) { builder.append(", hasHeader"); } @@ -784,6 +813,7 @@ public final class FillResponse implements Parcelable { parcel.writeParcelable(mAuthentication, flags); parcel.writeParcelable(mPresentation, flags); parcel.writeParcelable(mInlinePresentation, flags); + parcel.writeParcelable(mInlineTooltipPresentation, flags); parcel.writeParcelable(mHeader, flags); parcel.writeParcelable(mFooter, flags); parcel.writeParcelable(mUserData, flags); @@ -818,9 +848,10 @@ public final class FillResponse implements Parcelable { final IntentSender authentication = parcel.readParcelable(null); final RemoteViews presentation = parcel.readParcelable(null); final InlinePresentation inlinePresentation = parcel.readParcelable(null); + final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null); if (authenticationIds != null) { builder.setAuthentication(authenticationIds, authentication, presentation, - inlinePresentation); + inlinePresentation, inlineTooltipPresentation); } final RemoteViews header = parcel.readParcelable(null); if (header != null) { diff --git a/core/java/android/service/autofill/InlinePresentation.java b/core/java/android/service/autofill/InlinePresentation.java index fbf23b69addf4..40349576c460d 100644 --- a/core/java/android/service/autofill/InlinePresentation.java +++ b/core/java/android/service/autofill/InlinePresentation.java @@ -75,9 +75,23 @@ public final class InlinePresentation implements Parcelable { return hints.toArray(new String[hints.size()]); } + /** + * Creates a presentation for the inline suggestion tooltip + * + * @param slice Represents the UI content and the action for the inline suggestion tooltip. + * @param spec Specifies the UI specification for the inline suggestion tooltip. + * @return An {@link InlinePresentation} for the inline suggestion tooltip + */ + @NonNull + public static InlinePresentation createTooltipPresentation(@NonNull Slice slice, + @NonNull InlinePresentationSpec spec) { + return new InlinePresentation(slice, spec, /* pinned */ false); + + } - // Code below generated by codegen v1.0.20. + + // Code below generated by codegen v1.0.22. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -259,10 +273,10 @@ public final class InlinePresentation implements Parcelable { }; @DataClass.Generated( - time = 1604456277638L, - codegenVersion = "1.0.20", + time = 1615968415006L, + codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/service/autofill/InlinePresentation.java", - inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final boolean mPinned\npublic @android.annotation.NonNull @android.annotation.Size java.lang.String[] getAutofillHints()\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)") + inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final boolean mPinned\npublic @android.annotation.NonNull @android.annotation.Size java.lang.String[] getAutofillHints()\npublic static @android.annotation.NonNull android.service.autofill.InlinePresentation createTooltipPresentation(android.app.slice.Slice,android.widget.inline.InlinePresentationSpec)\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java index b8893cee834d5..27c637bb59dd7 100644 --- a/core/java/android/view/inputmethod/InlineSuggestion.java +++ b/core/java/android/view/inputmethod/InlineSuggestion.java @@ -31,6 +31,7 @@ import android.os.RemoteException; import android.util.Size; import android.util.Slog; import android.view.SurfaceControlViewHost; +import android.view.View; import android.view.ViewGroup; import android.widget.inline.InlineContentView; @@ -38,6 +39,7 @@ import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; import com.android.internal.view.inline.IInlineContentCallback; import com.android.internal.view.inline.IInlineContentProvider; +import com.android.internal.view.inline.InlineTooltipUi; import java.lang.ref.WeakReference; import java.util.concurrent.Executor; @@ -74,6 +76,15 @@ public final class InlineSuggestion implements Parcelable { @Nullable private InlineContentCallbackImpl mInlineContentCallback; + /** + * Used to show up the inline suggestion tooltip. + * + * @hide + */ + @Nullable + @DataClass.ParcelWith(InlineTooltipUiParceling.class) + private InlineTooltipUi mInlineTooltipUi; + /** * Creates a new {@link InlineSuggestion}, for testing purpose. * @@ -82,7 +93,8 @@ public final class InlineSuggestion implements Parcelable { @TestApi @NonNull public static InlineSuggestion newInlineSuggestion(@NonNull InlineSuggestionInfo info) { - return new InlineSuggestion(info, null, /* inlineContentCallback */ null); + return new InlineSuggestion(info, null, /* inlineContentCallback */ null, + /* inlineTooltipUi */ null); } /** @@ -92,7 +104,7 @@ public final class InlineSuggestion implements Parcelable { */ public InlineSuggestion(@NonNull InlineSuggestionInfo info, @Nullable IInlineContentProvider contentProvider) { - this(info, contentProvider, /* inlineContentCallback */ null); + this(info, contentProvider, /* inlineContentCallback */ null, /* inlineTooltipUi */ null); } /** @@ -136,9 +148,21 @@ public final class InlineSuggestion implements Parcelable { "size is neither between min:" + minSize + " and max:" + maxSize + ", nor wrap_content"); } - mInlineContentCallback = getInlineContentCallback(context, callbackExecutor, callback); + + InlineSuggestion toolTip = mInfo.getTooltip(); + if (toolTip != null) { + if (mInlineTooltipUi == null) { + mInlineTooltipUi = new InlineTooltipUi(context); + } + } else { + mInlineTooltipUi = null; + } + + mInlineContentCallback = getInlineContentCallback(context, callbackExecutor, callback, + mInlineTooltipUi); if (mContentProvider == null) { callbackExecutor.execute(() -> callback.accept(/* view */ null)); + mInlineTooltipUi = null; return; } try { @@ -148,6 +172,13 @@ public final class InlineSuggestion implements Parcelable { Slog.w(TAG, "Error creating suggestion content surface: " + e); callbackExecutor.execute(() -> callback.accept(/* view */ null)); } + if (toolTip == null) return; + + final Size tooltipSize = new Size(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + mInfo.getTooltip().inflate(context, tooltipSize, callbackExecutor, view -> { + Handler.getMain().post(() -> mInlineTooltipUi.setTooltipView(view)); + }); } /** @@ -162,12 +193,13 @@ public final class InlineSuggestion implements Parcelable { } private synchronized InlineContentCallbackImpl getInlineContentCallback(Context context, - Executor callbackExecutor, Consumer callback) { + Executor callbackExecutor, Consumer callback, + InlineTooltipUi inlineTooltipUi) { if (mInlineContentCallback != null) { throw new IllegalStateException("Already called #inflate()"); } return new InlineContentCallbackImpl(context, mContentProvider, callbackExecutor, - callback); + callback, inlineTooltipUi); } /** @@ -267,14 +299,19 @@ public final class InlineSuggestion implements Parcelable { @Nullable private Consumer mSurfacePackageConsumer; + @Nullable + private InlineTooltipUi mInlineTooltipUi; + InlineContentCallbackImpl(@NonNull Context context, @Nullable IInlineContentProvider inlineContentProvider, @NonNull @CallbackExecutor Executor callbackExecutor, - @NonNull Consumer callback) { + @NonNull Consumer callback, + @Nullable InlineTooltipUi inlineTooltipUi) { mContext = context; mInlineContentProvider = inlineContentProvider; mCallbackExecutor = callbackExecutor; mCallback = callback; + mInlineTooltipUi = inlineTooltipUi; } @BinderThread @@ -305,6 +342,17 @@ public final class InlineSuggestion implements Parcelable { mCallbackExecutor.execute(() -> mCallback.accept(/* view */null)); } else { mView = new InlineContentView(mContext); + if (mInlineTooltipUi != null) { + mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, + int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + if (mInlineTooltipUi != null) { + mInlineTooltipUi.update(mView); + } + } + }); + } mView.setLayoutParams(new ViewGroup.LayoutParams(width, height)); mView.setChildSurfacePackageUpdater(getSurfacePackageUpdater()); mCallbackExecutor.execute(() -> mCallback.accept(mView)); @@ -425,10 +473,25 @@ public final class InlineSuggestion implements Parcelable { } } + /** + * This class used to provide parcelling logic for InlineContentCallbackImpl. It's intended to + * make this parcelling a no-op, since it can't be parceled and we don't need to parcel it. + */ + private static class InlineTooltipUiParceling implements + Parcelling { + @Override + public void parcel(InlineTooltipUi item, Parcel dest, int parcelFlags) { + } + + @Override + public InlineTooltipUi unparcel(Parcel source) { + return null; + } + } - // Code below generated by codegen v1.0.15. + // Code below generated by codegen v1.0.22. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -446,18 +509,22 @@ public final class InlineSuggestion implements Parcelable { * * @param inlineContentCallback * Used to keep a strong reference to the callback so it doesn't get garbage collected. + * @param inlineTooltipUi + * Used to show up the inline suggestion tooltip. * @hide */ @DataClass.Generated.Member public InlineSuggestion( @NonNull InlineSuggestionInfo info, @Nullable IInlineContentProvider contentProvider, - @Nullable InlineContentCallbackImpl inlineContentCallback) { + @Nullable InlineContentCallbackImpl inlineContentCallback, + @Nullable InlineTooltipUi inlineTooltipUi) { this.mInfo = info; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mInfo); this.mContentProvider = contentProvider; this.mInlineContentCallback = inlineContentCallback; + this.mInlineTooltipUi = inlineTooltipUi; // onConstructed(); // You can define this method to get a callback } @@ -485,6 +552,16 @@ public final class InlineSuggestion implements Parcelable { return mInlineContentCallback; } + /** + * Used to show up the inline suggestion tooltip. + * + * @hide + */ + @DataClass.Generated.Member + public @Nullable InlineTooltipUi getInlineTooltipUi() { + return mInlineTooltipUi; + } + @Override @DataClass.Generated.Member public String toString() { @@ -494,7 +571,8 @@ public final class InlineSuggestion implements Parcelable { return "InlineSuggestion { " + "info = " + mInfo + ", " + "contentProvider = " + mContentProvider + ", " + - "inlineContentCallback = " + mInlineContentCallback + + "inlineContentCallback = " + mInlineContentCallback + ", " + + "inlineTooltipUi = " + mInlineTooltipUi + " }"; } @@ -513,7 +591,8 @@ public final class InlineSuggestion implements Parcelable { return true && java.util.Objects.equals(mInfo, that.mInfo) && java.util.Objects.equals(mContentProvider, that.mContentProvider) - && java.util.Objects.equals(mInlineContentCallback, that.mInlineContentCallback); + && java.util.Objects.equals(mInlineContentCallback, that.mInlineContentCallback) + && java.util.Objects.equals(mInlineTooltipUi, that.mInlineTooltipUi); } @Override @@ -526,6 +605,7 @@ public final class InlineSuggestion implements Parcelable { _hash = 31 * _hash + java.util.Objects.hashCode(mInfo); _hash = 31 * _hash + java.util.Objects.hashCode(mContentProvider); _hash = 31 * _hash + java.util.Objects.hashCode(mInlineContentCallback); + _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipUi); return _hash; } @@ -540,6 +620,17 @@ public final class InlineSuggestion implements Parcelable { } } + @DataClass.Generated.Member + static Parcelling sParcellingForInlineTooltipUi = + Parcelling.Cache.get( + InlineTooltipUiParceling.class); + static { + if (sParcellingForInlineTooltipUi == null) { + sParcellingForInlineTooltipUi = Parcelling.Cache.put( + new InlineTooltipUiParceling()); + } + } + @Override @DataClass.Generated.Member public void writeToParcel(@NonNull Parcel dest, int flags) { @@ -549,10 +640,12 @@ public final class InlineSuggestion implements Parcelable { byte flg = 0; if (mContentProvider != null) flg |= 0x2; if (mInlineContentCallback != null) flg |= 0x4; + if (mInlineTooltipUi != null) flg |= 0x8; dest.writeByte(flg); dest.writeTypedObject(mInfo, flags); if (mContentProvider != null) dest.writeStrongInterface(mContentProvider); sParcellingForInlineContentCallback.parcel(mInlineContentCallback, dest, flags); + sParcellingForInlineTooltipUi.parcel(mInlineTooltipUi, dest, flags); } @Override @@ -570,12 +663,14 @@ public final class InlineSuggestion implements Parcelable { InlineSuggestionInfo info = (InlineSuggestionInfo) in.readTypedObject(InlineSuggestionInfo.CREATOR); IInlineContentProvider contentProvider = (flg & 0x2) == 0 ? null : IInlineContentProvider.Stub.asInterface(in.readStrongBinder()); InlineContentCallbackImpl inlineContentCallback = sParcellingForInlineContentCallback.unparcel(in); + InlineTooltipUi inlineTooltipUi = sParcellingForInlineTooltipUi.unparcel(in); this.mInfo = info; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mInfo); this.mContentProvider = contentProvider; this.mInlineContentCallback = inlineContentCallback; + this.mInlineTooltipUi = inlineTooltipUi; // onConstructed(); // You can define this method to get a callback } @@ -595,10 +690,10 @@ public final class InlineSuggestion implements Parcelable { }; @DataClass.Generated( - time = 1589396017700L, - codegenVersion = "1.0.15", + time = 1615562097666L, + codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java", - inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\nprivate @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineContentCallbackImplParceling.class) @android.annotation.Nullable android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl mInlineContentCallback\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer)\nprivate static boolean isValid(int,int,int)\nprivate synchronized android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") + inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\nprivate @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineContentCallbackImplParceling.class) @android.annotation.Nullable android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl mInlineContentCallback\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineTooltipUiParceling.class) com.android.internal.view.inline.InlineTooltipUi mInlineTooltipUi\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer)\nprivate static boolean isValid(int,int,int)\nprivate synchronized android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer,com.android.internal.view.inline.InlineTooltipUi)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/inputmethod/InlineSuggestionInfo.java b/core/java/android/view/inputmethod/InlineSuggestionInfo.java index 10fd0e0368142..5798614b8c9f7 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java +++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java @@ -72,6 +72,11 @@ public final class InlineSuggestionInfo implements Parcelable { /** Whether the suggestion should be pinned or not. */ private final boolean mPinned; + /** + * @hide + */ + private final @Nullable InlineSuggestion mTooltip; + /** * Creates a new {@link InlineSuggestionInfo}, for testing purpose. * @@ -84,12 +89,30 @@ public final class InlineSuggestionInfo implements Parcelable { @NonNull @Source String source, @SuppressLint("NullableCollection") @Nullable String[] autofillHints, @NonNull @Type String type, boolean isPinned) { - return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned); + return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned, + null); + } + + /** + * Creates a new {@link InlineSuggestionInfo}, for testing purpose. + * + * @hide + */ + @NonNull + public static InlineSuggestionInfo newInlineSuggestionInfo( + @NonNull InlinePresentationSpec presentationSpec, + @NonNull @Source String source, + @Nullable String[] autofillHints, @NonNull @Type String type, boolean isPinned, + @Nullable InlineSuggestion tooltip) { + return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned, + tooltip); } - // Code below generated by codegen v1.0.20. + + + // Code below generated by codegen v1.0.22. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -141,7 +164,8 @@ public final class InlineSuggestionInfo implements Parcelable { @NonNull @Source String source, @Nullable String[] autofillHints, @NonNull @Type String type, - boolean pinned) { + boolean pinned, + @Nullable InlineSuggestion tooltip) { this.mInlinePresentationSpec = inlinePresentationSpec; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mInlinePresentationSpec); @@ -171,6 +195,7 @@ public final class InlineSuggestionInfo implements Parcelable { com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mType); this.mPinned = pinned; + this.mTooltip = tooltip; // onConstructed(); // You can define this method to get a callback } @@ -215,6 +240,14 @@ public final class InlineSuggestionInfo implements Parcelable { return mPinned; } + /** + * @hide + */ + @DataClass.Generated.Member + public @Nullable InlineSuggestion getTooltip() { + return mTooltip; + } + @Override @DataClass.Generated.Member public String toString() { @@ -226,7 +259,8 @@ public final class InlineSuggestionInfo implements Parcelable { "source = " + mSource + ", " + "autofillHints = " + java.util.Arrays.toString(mAutofillHints) + ", " + "type = " + mType + ", " + - "pinned = " + mPinned + + "pinned = " + mPinned + ", " + + "tooltip = " + mTooltip + " }"; } @@ -247,7 +281,8 @@ public final class InlineSuggestionInfo implements Parcelable { && java.util.Objects.equals(mSource, that.mSource) && java.util.Arrays.equals(mAutofillHints, that.mAutofillHints) && java.util.Objects.equals(mType, that.mType) - && mPinned == that.mPinned; + && mPinned == that.mPinned + && java.util.Objects.equals(mTooltip, that.mTooltip); } @Override @@ -262,6 +297,7 @@ public final class InlineSuggestionInfo implements Parcelable { _hash = 31 * _hash + java.util.Arrays.hashCode(mAutofillHints); _hash = 31 * _hash + java.util.Objects.hashCode(mType); _hash = 31 * _hash + Boolean.hashCode(mPinned); + _hash = 31 * _hash + java.util.Objects.hashCode(mTooltip); return _hash; } @@ -274,11 +310,13 @@ public final class InlineSuggestionInfo implements Parcelable { byte flg = 0; if (mPinned) flg |= 0x10; if (mAutofillHints != null) flg |= 0x4; + if (mTooltip != null) flg |= 0x20; dest.writeByte(flg); dest.writeTypedObject(mInlinePresentationSpec, flags); dest.writeString(mSource); if (mAutofillHints != null) dest.writeStringArray(mAutofillHints); dest.writeString(mType); + if (mTooltip != null) dest.writeTypedObject(mTooltip, flags); } @Override @@ -298,6 +336,7 @@ public final class InlineSuggestionInfo implements Parcelable { String source = in.readString(); String[] autofillHints = (flg & 0x4) == 0 ? null : in.createStringArray(); String type = in.readString(); + InlineSuggestion tooltip = (flg & 0x20) == 0 ? null : (InlineSuggestion) in.readTypedObject(InlineSuggestion.CREATOR); this.mInlinePresentationSpec = inlinePresentationSpec; com.android.internal.util.AnnotationValidations.validate( @@ -328,6 +367,7 @@ public final class InlineSuggestionInfo implements Parcelable { com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mType); this.mPinned = pinned; + this.mTooltip = tooltip; // onConstructed(); // You can define this method to get a callback } @@ -347,10 +387,10 @@ public final class InlineSuggestionInfo implements Parcelable { }; @DataClass.Generated( - time = 1604456249219L, - codegenVersion = "1.0.20", + time = 1614287616672L, + codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionInfo.java", - inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_SUGGESTION\npublic static final @android.annotation.SuppressLint @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_ACTION\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String mType\nprivate final boolean mPinned\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.widget.inline.InlinePresentationSpec,java.lang.String,java.lang.String[],java.lang.String,boolean)\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") + inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_SUGGESTION\npublic static final @android.annotation.SuppressLint @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String TYPE_ACTION\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Type java.lang.String mType\nprivate final boolean mPinned\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestion mTooltip\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.widget.inline.InlinePresentationSpec,java.lang.String,java.lang.String[],java.lang.String,boolean)\npublic static @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.widget.inline.InlinePresentationSpec,java.lang.String,java.lang.String[],java.lang.String,boolean,android.view.inputmethod.InlineSuggestion)\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java index 0ab4e05227ba5..e1e175512edc9 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java +++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java @@ -105,6 +105,27 @@ public final class InlineSuggestionsRequest implements Parcelable { */ private int mHostDisplayId; + /** + * Specifies the UI specification for the inline suggestion tooltip in the response. + */ + private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec; + + /** + * Whether the IME supports inline suggestions from the default Autofill service that + * provides the input view. + * + * Note: The default value is {@code true}. + */ + private boolean mServiceSupported; + + /** + * Whether the IME supports inline suggestions from the application that provides the + * input view. + * + * Note: The default value is {@code true}. + */ + private boolean mClientSupported; + /** * @hide * @see {@link #mHostInputToken}. @@ -151,6 +172,10 @@ public final class InlineSuggestionsRequest implements Parcelable { for (int i = 0; i < mInlinePresentationSpecs.size(); i++) { mInlinePresentationSpecs.get(i).filterContentTypes(); } + + if (mInlineTooltipPresentationSpec != null) { + mInlineTooltipPresentationSpec.filterContentTypes(); + } } private static int defaultMaxSuggestionCount() { @@ -161,6 +186,10 @@ public final class InlineSuggestionsRequest implements Parcelable { return ActivityThread.currentPackageName(); } + private static InlinePresentationSpec defaultInlineTooltipPresentationSpec() { + return null; + } + /** * The {@link InlineSuggestionsRequest#getSupportedLocales()} now returns empty locale list when * it's not set, instead of the default system locale. @@ -191,6 +220,14 @@ public final class InlineSuggestionsRequest implements Parcelable { return Bundle.EMPTY; } + private static boolean defaultServiceSupported() { + return true; + } + + private static boolean defaultClientSupported() { + return true; + } + /** @hide */ abstract static class BaseBuilder { abstract Builder setInlinePresentationSpecs( @@ -203,6 +240,16 @@ public final class InlineSuggestionsRequest implements Parcelable { abstract Builder setHostDisplayId(int value); } + /** @hide */ + public boolean isServiceSupported() { + return mServiceSupported; + } + + /** @hide */ + public boolean isClientSupported() { + return mClientSupported; + } + // Code below generated by codegen v1.0.22. @@ -226,7 +273,10 @@ public final class InlineSuggestionsRequest implements Parcelable { @NonNull LocaleList supportedLocales, @NonNull Bundle extras, @Nullable IBinder hostInputToken, - int hostDisplayId) { + int hostDisplayId, + @Nullable InlinePresentationSpec inlineTooltipPresentationSpec, + boolean serviceSupported, + boolean clientSupported) { this.mMaxSuggestionCount = maxSuggestionCount; this.mInlinePresentationSpecs = inlinePresentationSpecs; com.android.internal.util.AnnotationValidations.validate( @@ -242,6 +292,9 @@ public final class InlineSuggestionsRequest implements Parcelable { NonNull.class, null, mExtras); this.mHostInputToken = hostInputToken; this.mHostDisplayId = hostDisplayId; + this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec; + this.mServiceSupported = serviceSupported; + this.mClientSupported = clientSupported; onConstructed(); } @@ -324,6 +377,16 @@ public final class InlineSuggestionsRequest implements Parcelable { return mHostDisplayId; } + /** + * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response. + * + * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec) + */ + @DataClass.Generated.Member + public @Nullable InlinePresentationSpec getInlineTooltipPresentationSpec() { + return mInlineTooltipPresentationSpec; + } + @Override @DataClass.Generated.Member public String toString() { @@ -337,7 +400,10 @@ public final class InlineSuggestionsRequest implements Parcelable { "supportedLocales = " + mSupportedLocales + ", " + "extras = " + mExtras + ", " + "hostInputToken = " + mHostInputToken + ", " + - "hostDisplayId = " + mHostDisplayId + + "hostDisplayId = " + mHostDisplayId + ", " + + "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + ", " + + "serviceSupported = " + mServiceSupported + ", " + + "clientSupported = " + mClientSupported + " }"; } @@ -360,7 +426,10 @@ public final class InlineSuggestionsRequest implements Parcelable { && java.util.Objects.equals(mSupportedLocales, that.mSupportedLocales) && extrasEquals(that.mExtras) && java.util.Objects.equals(mHostInputToken, that.mHostInputToken) - && mHostDisplayId == that.mHostDisplayId; + && mHostDisplayId == that.mHostDisplayId + && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec) + && mServiceSupported == that.mServiceSupported + && mClientSupported == that.mClientSupported; } @Override @@ -377,6 +446,9 @@ public final class InlineSuggestionsRequest implements Parcelable { _hash = 31 * _hash + java.util.Objects.hashCode(mExtras); _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken); _hash = 31 * _hash + mHostDisplayId; + _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipPresentationSpec); + _hash = 31 * _hash + Boolean.hashCode(mServiceSupported); + _hash = 31 * _hash + Boolean.hashCode(mClientSupported); return _hash; } @@ -386,9 +458,12 @@ public final class InlineSuggestionsRequest implements Parcelable { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } - byte flg = 0; + int flg = 0; + if (mServiceSupported) flg |= 0x100; + if (mClientSupported) flg |= 0x200; if (mHostInputToken != null) flg |= 0x20; - dest.writeByte(flg); + if (mInlineTooltipPresentationSpec != null) flg |= 0x80; + dest.writeInt(flg); dest.writeInt(mMaxSuggestionCount); dest.writeParcelableList(mInlinePresentationSpecs, flags); dest.writeString(mHostPackageName); @@ -396,6 +471,7 @@ public final class InlineSuggestionsRequest implements Parcelable { dest.writeBundle(mExtras); parcelHostInputToken(dest, flags); dest.writeInt(mHostDisplayId); + if (mInlineTooltipPresentationSpec != null) dest.writeTypedObject(mInlineTooltipPresentationSpec, flags); } @Override @@ -409,7 +485,9 @@ public final class InlineSuggestionsRequest implements Parcelable { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } - byte flg = in.readByte(); + int flg = in.readInt(); + boolean serviceSupported = (flg & 0x100) != 0; + boolean clientSupported = (flg & 0x200) != 0; int maxSuggestionCount = in.readInt(); List inlinePresentationSpecs = new ArrayList<>(); in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader()); @@ -418,6 +496,7 @@ public final class InlineSuggestionsRequest implements Parcelable { Bundle extras = in.readBundle(); IBinder hostInputToken = unparcelHostInputToken(in); int hostDisplayId = in.readInt(); + InlinePresentationSpec inlineTooltipPresentationSpec = (flg & 0x80) == 0 ? null : (InlinePresentationSpec) in.readTypedObject(InlinePresentationSpec.CREATOR); this.mMaxSuggestionCount = maxSuggestionCount; this.mInlinePresentationSpecs = inlinePresentationSpecs; @@ -434,6 +513,9 @@ public final class InlineSuggestionsRequest implements Parcelable { NonNull.class, null, mExtras); this.mHostInputToken = hostInputToken; this.mHostDisplayId = hostDisplayId; + this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec; + this.mServiceSupported = serviceSupported; + this.mClientSupported = clientSupported; onConstructed(); } @@ -466,6 +548,9 @@ public final class InlineSuggestionsRequest implements Parcelable { private @NonNull Bundle mExtras; private @Nullable IBinder mHostInputToken; private int mHostDisplayId; + private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec; + private boolean mServiceSupported; + private boolean mClientSupported; private long mBuilderFieldsSet = 0L; @@ -597,10 +682,51 @@ public final class InlineSuggestionsRequest implements Parcelable { return this; } + /** + * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response. + * + * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)s + */ + @DataClass.Generated.Member + public @NonNull Builder setInlineTooltipPresentationSpec(@NonNull InlinePresentationSpec value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x80; + mInlineTooltipPresentationSpec = value; + return this; + } + + /** + * Whether the IME supports inline suggestions from the default Autofill service that + * provides the input view. + * + * Note: The default value is {@code true}. + */ + @DataClass.Generated.Member + public @NonNull Builder setServiceSupported(boolean value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x100; + mServiceSupported = value; + return this; + } + + /** + * Whether the IME supports inline suggestions from the application that provides the + * input view. + * + * Note: The default value is {@code true}. + */ + @DataClass.Generated.Member + public @NonNull Builder setClientSupported(boolean value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x200; + mClientSupported = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull InlineSuggestionsRequest build() { checkNotUsed(); - mBuilderFieldsSet |= 0x80; // Mark builder used + mBuilderFieldsSet |= 0x400; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mMaxSuggestionCount = defaultMaxSuggestionCount(); @@ -620,6 +746,15 @@ public final class InlineSuggestionsRequest implements Parcelable { if ((mBuilderFieldsSet & 0x40) == 0) { mHostDisplayId = defaultHostDisplayId(); } + if ((mBuilderFieldsSet & 0x80) == 0) { + mInlineTooltipPresentationSpec = defaultInlineTooltipPresentationSpec(); + } + if ((mBuilderFieldsSet & 0x100) == 0) { + mServiceSupported = defaultServiceSupported(); + } + if ((mBuilderFieldsSet & 0x200) == 0) { + mClientSupported = defaultClientSupported(); + } InlineSuggestionsRequest o = new InlineSuggestionsRequest( mMaxSuggestionCount, mInlinePresentationSpecs, @@ -627,12 +762,15 @@ public final class InlineSuggestionsRequest implements Parcelable { mSupportedLocales, mExtras, mHostInputToken, - mHostDisplayId); + mHostDisplayId, + mInlineTooltipPresentationSpec, + mServiceSupported, + mClientSupported); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x80) != 0) { + if ((mBuilderFieldsSet & 0x400) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -640,10 +778,10 @@ public final class InlineSuggestionsRequest implements Parcelable { } @DataClass.Generated( - time = 1612206506050L, + time = 1615798784918L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java", - inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\npublic void filterContentTypes()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []") + inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate boolean mServiceSupported\nprivate boolean mClientSupported\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\npublic void filterContentTypes()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nprivate static boolean defaultServiceSupported()\nprivate static boolean defaultClientSupported()\npublic boolean isServiceSupported()\npublic boolean isClientSupported()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} diff --git a/core/java/com/android/internal/view/inline/InlineTooltipUi.java b/core/java/com/android/internal/view/inline/InlineTooltipUi.java new file mode 100644 index 0000000000000..5ec8b30d6a7bf --- /dev/null +++ b/core/java/com/android/internal/view/inline/InlineTooltipUi.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2021 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.internal.view.inline; + +import static android.view.autofill.Helper.sVerbose; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.ContextWrapper; +import android.graphics.drawable.Drawable; +import android.transition.Transition; +import android.util.Slog; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.LinearLayout; +import android.widget.PopupWindow; +import android.widget.inline.InlineContentView; + +import java.io.PrintWriter; + +/** + * UI container for the inline suggestion tooltip. + */ +public final class InlineTooltipUi extends PopupWindow implements AutoCloseable { + private static final String TAG = "InlineTooltipUi"; + + private final WindowManager mWm; + private final ViewGroup mContentContainer; + + private boolean mShowing; + + private WindowManager.LayoutParams mWindowLayoutParams; + + private final View.OnAttachStateChangeListener mAnchorOnAttachStateChangeListener = + new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + /* ignore - handled by the super class */ + } + + @Override + public void onViewDetachedFromWindow(View v) { + dismiss(); + } + }; + + private final View.OnLayoutChangeListener mAnchoredOnLayoutChangeListener = + new View.OnLayoutChangeListener() { + int mHeight; + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) { + if (mHeight != bottom - top) { + mHeight = bottom - top; + adjustPosition(); + } + } + }; + + public InlineTooltipUi(@NonNull Context context) { + mContentContainer = new LinearLayout(new ContextWrapper(context)); + mWm = context.getSystemService(WindowManager.class); + + setTouchModal(false); + setOutsideTouchable(true); + setInputMethodMode(INPUT_METHOD_NOT_NEEDED); + setFocusable(false); + } + + /** + * Sets the content view for inline suggestions tooltip + * @param v the content view of {@link android.widget.inline.InlineContentView} + */ + public void setTooltipView(@NonNull InlineContentView v) { + mContentContainer.removeAllViews(); + mContentContainer.addView(v); + mContentContainer.setVisibility(View.VISIBLE); + } + + @Override + public void close() { + hide(); + } + + @Override + protected boolean hasContentView() { + return true; + } + + @Override + protected boolean hasDecorView() { + return true; + } + + @Override + protected WindowManager.LayoutParams getDecorViewLayoutParams() { + return mWindowLayoutParams; + } + + /** + * The effective {@code update} method that should be called by its clients. + */ + public void update(View anchor) { + // set to the application type with the highest z-order + setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL); + + // The first time to show up, the height of tooltip is zero, + // so set the offset Y to 2 * anchor height. + final int achoredHeight = mContentContainer.getHeight(); + final int offsetY = (achoredHeight == 0) + ? -anchor.getHeight() << 1 : -anchor.getHeight() - achoredHeight; + if (!isShowing()) { + setWidth(WindowManager.LayoutParams.WRAP_CONTENT); + setHeight(WindowManager.LayoutParams.WRAP_CONTENT); + showAsDropDown(anchor, 0 , offsetY, Gravity.TOP | Gravity.CENTER_HORIZONTAL); + } else { + update(anchor, 0 , offsetY, WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT); + } + } + + @Override + protected void update(View anchor, WindowManager.LayoutParams params) { + // update content view for the anchor is scrolling + if (anchor.isVisibleToUser()) { + show(params); + } else { + hide(); + } + } + + @Override + public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { + if (isShowing()) { + return; + } + + setShowing(true); + setDropDown(true); + attachToAnchor(anchor, xoff, yoff, gravity); + final WindowManager.LayoutParams p = mWindowLayoutParams = createPopupLayoutParams( + anchor.getWindowToken()); + final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, + p.width, p.height, gravity, getAllowScrollingAnchorParent()); + updateAboveAnchor(aboveAnchor); + p.accessibilityIdOfAnchor = anchor.getAccessibilityViewId(); + p.packageName = anchor.getContext().getPackageName(); + show(p); + } + + @Override + protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) { + super.attachToAnchor(anchor, xoff, yoff, gravity); + anchor.addOnAttachStateChangeListener(mAnchorOnAttachStateChangeListener); + } + + @Override + protected void detachFromAnchor() { + final View anchor = getAnchor(); + if (anchor != null) { + anchor.removeOnAttachStateChangeListener(mAnchorOnAttachStateChangeListener); + } + super.detachFromAnchor(); + } + + @Override + public void dismiss() { + if (!isShowing() || isTransitioningToDismiss()) { + return; + } + + setShowing(false); + setTransitioningToDismiss(true); + + hide(); + detachFromAnchor(); + if (getOnDismissListener() != null) { + getOnDismissListener().onDismiss(); + } + } + + private void adjustPosition() { + View anchor = getAnchor(); + if (anchor == null) return; + update(anchor); + } + + private void show(WindowManager.LayoutParams params) { + if (sVerbose) { + Slog.v(TAG, "show()"); + } + mWindowLayoutParams = params; + + try { + params.packageName = "android"; + params.setTitle("Autofill Inline Tooltip"); // Title is set for debugging purposes + if (!mShowing) { + params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + mContentContainer.addOnLayoutChangeListener(mAnchoredOnLayoutChangeListener); + mWm.addView(mContentContainer, params); + mShowing = true; + } else { + mWm.updateViewLayout(mContentContainer, params); + } + } catch (WindowManager.BadTokenException e) { + Slog.d(TAG, "Failed with token " + params.token + " gone."); + } catch (IllegalStateException e) { + // WM throws an ISE if mContentView was added twice; this should never happen - + // since show() and hide() are always called in the UIThread - but when it does, + // it should not crash the system. + Slog.wtf(TAG, "Exception showing window " + params, e); + } + } + + private void hide() { + if (sVerbose) { + Slog.v(TAG, "hide()"); + } + try { + if (mShowing) { + mContentContainer.removeOnLayoutChangeListener(mAnchoredOnLayoutChangeListener); + mWm.removeView(mContentContainer); + mShowing = false; + } + } catch (IllegalStateException e) { + // WM might thrown an ISE when removing the mContentView; this should never + // happen - since show() and hide() are always called in the UIThread - but if it + // does, it should not crash the system. + Slog.e(TAG, "Exception hiding window ", e); + } + } + + @Override + public int getAnimationStyle() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public Drawable getBackground() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public View getContentView() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public float getElevation() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public Transition getEnterTransition() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public Transition getExitTransition() { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setBackgroundDrawable(Drawable background) { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setContentView(View contentView) { + if (contentView != null) { + throw new IllegalStateException("You can't call this!"); + } + } + + @Override + public void setElevation(float elevation) { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setEnterTransition(Transition enterTransition) { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setExitTransition(Transition exitTransition) { + throw new IllegalStateException("You can't call this!"); + } + + @Override + public void setTouchInterceptor(View.OnTouchListener l) { + throw new IllegalStateException("You can't call this!"); + } + + /** + * Dumps status + */ + public void dump(@NonNull PrintWriter pw, @Nullable String prefix) { + + pw.print(prefix); + + if (mContentContainer != null) { + pw.print(prefix); pw.print("Window: "); + final String prefix2 = prefix + " "; + pw.println(); + pw.print(prefix2); pw.print("showing: "); pw.println(mShowing); + pw.print(prefix2); pw.print("view: "); pw.println(mContentContainer); + if (mWindowLayoutParams != null) { + pw.print(prefix2); pw.print("params: "); pw.println(mWindowLayoutParams); + } + pw.print(prefix2); pw.print("screen coordinates: "); + if (mContentContainer == null) { + pw.println("N/A"); + } else { + final int[] coordinates = mContentContainer.getLocationOnScreen(); + pw.print(coordinates[0]); pw.print("x"); pw.println(coordinates[1]); + } + } + } +} diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java index b15d07b0f1a94..9d4c9ebbf5922 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java @@ -16,10 +16,13 @@ package com.android.server.autofill.ui; +import static android.view.inputmethod.InlineSuggestionInfo.TYPE_SUGGESTION; + import static com.android.server.autofill.Helper.sDebug; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.IntentSender; import android.service.autofill.Dataset; import android.service.autofill.FillResponse; import android.service.autofill.InlinePresentation; @@ -49,6 +52,9 @@ final class InlineSuggestionFactory { InlineSuggestionInfo.TYPE_ACTION, () -> uiCallback.authenticate(requestId, AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED), mergedInlinePresentation(inlineFillUiInfo.mInlineRequest, 0, inlineAuthentication), + createInlineSuggestionTooltip(inlineFillUiInfo.mInlineRequest, + inlineFillUiInfo, InlineSuggestionInfo.SOURCE_AUTOFILL, + response.getInlineTooltipPresentation()), uiCallback); } @@ -66,6 +72,8 @@ final class InlineSuggestionFactory { final InlineSuggestionsRequest request = inlineFillUiInfo.mInlineRequest; SparseArray> response = new SparseArray<>(datasets.size()); + + boolean hasTooltip = false; for (int datasetIndex = 0; datasetIndex < datasets.size(); datasetIndex++) { final Dataset dataset = datasets.get(datasetIndex); final int fieldIndex = dataset.getFieldIds().indexOf(inlineFillUiInfo.mFocusedId); @@ -82,14 +90,25 @@ final class InlineSuggestionFactory { } final String suggestionType = - dataset.getAuthentication() == null ? InlineSuggestionInfo.TYPE_SUGGESTION + dataset.getAuthentication() == null ? TYPE_SUGGESTION : InlineSuggestionInfo.TYPE_ACTION; final int index = datasetIndex; + InlineSuggestion inlineSuggestionTooltip = null; + if (!hasTooltip) { + // Only available for first one inline suggestion tooltip. + inlineSuggestionTooltip = createInlineSuggestionTooltip(request, + inlineFillUiInfo, suggestionSource, + dataset.getFieldInlineTooltipPresentation(fieldIndex)); + if (inlineSuggestionTooltip != null) { + hasTooltip = true; + } + } InlineSuggestion inlineSuggestion = createInlineSuggestion( inlineFillUiInfo, suggestionSource, suggestionType, () -> uiCallback.autofill(dataset, index), mergedInlinePresentation(request, datasetIndex, inlinePresentation), + inlineSuggestionTooltip, uiCallback); response.append(datasetIndex, Pair.create(dataset, inlineSuggestion)); } @@ -103,11 +122,13 @@ final class InlineSuggestionFactory { @NonNull @InlineSuggestionInfo.Type String suggestionType, @NonNull Runnable onClickAction, @NonNull InlinePresentation inlinePresentation, + @Nullable InlineSuggestion tooltip, @NonNull InlineFillUi.InlineSuggestionUiCallback uiCallback) { + final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo( inlinePresentation.getInlinePresentationSpec(), suggestionSource, inlinePresentation.getAutofillHints(), suggestionType, - inlinePresentation.isPinned()); + inlinePresentation.isPinned(), tooltip); return new InlineSuggestion(inlineSuggestionInfo, createInlineContentProvider(inlineFillUiInfo, inlinePresentation, @@ -135,6 +156,60 @@ final class InlineSuggestionFactory { inlinePresentation.isPinned()); } + // TODO(182306770): creates new class instead of the InlineSuggestion. + private static InlineSuggestion createInlineSuggestionTooltip( + @NonNull InlineSuggestionsRequest request, + @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo, + String suggestionSource, + @NonNull InlinePresentation tooltipPresentation) { + if (tooltipPresentation == null) { + return null; + } + + final InlinePresentationSpec spec = request.getInlineTooltipPresentationSpec(); + InlinePresentationSpec mergedSpec; + if (spec == null) { + mergedSpec = tooltipPresentation.getInlinePresentationSpec(); + } else { + mergedSpec = new InlinePresentationSpec.Builder( + tooltipPresentation.getInlinePresentationSpec().getMinSize(), + tooltipPresentation.getInlinePresentationSpec().getMaxSize()).setStyle( + spec.getStyle()).build(); + } + + InlineFillUi.InlineSuggestionUiCallback uiCallback = + new InlineFillUi.InlineSuggestionUiCallback() { + @Override + public void autofill(Dataset dataset, int datasetIndex) { + /* nothing */ + } + + @Override + public void authenticate(int requestId, int datasetIndex) { + /* nothing */ + } + + @Override + public void startIntentSender(IntentSender intentSender) { + /* nothing */ + } + + @Override + public void onError() { + Slog.w(TAG, "An error happened on the tooltip"); + } + }; + + InlinePresentation tooltipInline = new InlinePresentation(tooltipPresentation.getSlice(), + mergedSpec, false); + IInlineContentProvider tooltipContentProvider = createInlineContentProvider( + inlineFillUiInfo, tooltipInline, () -> { /* no operation */ }, uiCallback); + final InlineSuggestionInfo tooltipInlineSuggestionInfo = new InlineSuggestionInfo( + mergedSpec, suggestionSource, /* autofillHints */ null, TYPE_SUGGESTION, + /* pinned */ false, /* tooltip */ null); + return new InlineSuggestion(tooltipInlineSuggestionInfo, tooltipContentProvider); + } + private static IInlineContentProvider createInlineContentProvider( @NonNull InlineFillUi.InlineFillUiInfo inlineFillUiInfo, @NonNull InlinePresentation inlinePresentation, @Nullable Runnable onClickAction,