All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.view.inputmethod.InlineSuggestion Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * 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 android.view.inputmethod;

import android.annotation.BinderThread;
import android.annotation.CallbackExecutor;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
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;

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;
import java.util.function.Consumer;

/**
 * This class represents an inline suggestion which is made by one app and can be embedded into the
 * UI of another. Suggestions may contain sensitive information not known to the host app which
 * needs to be protected from spoofing. To address that the suggestion view inflated on demand for
 * embedding is created in such a way that the hosting app cannot introspect its content and cannot
 * interact with it.
 */
@DataClass(genEqualsHashCode = true, genToString = true, genHiddenConstDefs = true,
        genHiddenConstructor = true)
public final class InlineSuggestion implements Parcelable {

    private static final String TAG = "InlineSuggestion";

    @NonNull
    private final InlineSuggestionInfo mInfo;

    /**
     * @hide
     */
    @Nullable
    private final IInlineContentProvider mContentProvider;

    /**
     * Used to keep a strong reference to the callback so it doesn't get garbage collected.
     *
     * @hide
     */
    @DataClass.ParcelWith(InlineContentCallbackImplParceling.class)
    @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.
     *
     * @hide
     */
    @TestApi
    @NonNull
    public static InlineSuggestion newInlineSuggestion(@NonNull InlineSuggestionInfo info) {
        return new InlineSuggestion(info, null, /* inlineContentCallback */ null,
                /* inlineTooltipUi */ null);
    }

    /**
     * Creates a new {@link InlineSuggestion}.
     *
     * @hide
     */
    public InlineSuggestion(@NonNull InlineSuggestionInfo info,
            @Nullable IInlineContentProvider contentProvider) {
        this(info, contentProvider, /* inlineContentCallback */ null, /* inlineTooltipUi */ null);
    }

    /**
     * Inflates a view with the content of this suggestion at a specific size.
     *
     * 

Each dimension of the size must satisfy one of the following conditions: * *

    *
  1. between {@link android.widget.inline.InlinePresentationSpec#getMinSize()} and * {@link android.widget.inline.InlinePresentationSpec#getMaxSize()} of the presentation spec * from {@code mInfo} *
  2. {@link ViewGroup.LayoutParams#WRAP_CONTENT} *
* * If the size is set to {@link * ViewGroup.LayoutParams#WRAP_CONTENT}, then the size of the inflated view will be just large * enough to fit the content, while still conforming to the min / max size specified by the * {@link android.widget.inline.InlinePresentationSpec}. * *

The caller can attach an {@link android.view.View.OnClickListener} and/or an * {@link android.view.View.OnLongClickListener} to the view in the {@code callback} to receive * click and long click events on the view. * * @param context Context in which to inflate the view. * @param size The size at which to inflate the suggestion. For each dimension, it maybe an * exact value or {@link ViewGroup.LayoutParams#WRAP_CONTENT}. * @param callback Callback for receiving the inflated view, where the {@link * ViewGroup.LayoutParams} of the view is set as the actual size of the * underlying remote view. * @throws IllegalArgumentException If an invalid argument is passed. * @throws IllegalStateException If this method is already called. */ public void inflate(@NonNull Context context, @NonNull Size size, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull Consumer callback) { final Size minSize = mInfo.getInlinePresentationSpec().getMinSize(); final Size maxSize = mInfo.getInlinePresentationSpec().getMaxSize(); if (!isValid(size.getWidth(), minSize.getWidth(), maxSize.getWidth()) || !isValid(size.getHeight(), minSize.getHeight(), maxSize.getHeight())) { throw new IllegalArgumentException( "size is neither between min:" + minSize + " and max:" + maxSize + ", nor wrap_content"); } 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 { mContentProvider.provideContent(size.getWidth(), size.getHeight(), new InlineContentCallbackWrapper(mInlineContentCallback)); } catch (RemoteException e) { 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)); }); } /** * Returns true if the {@code actual} length is within [min, max] or is {@link * ViewGroup.LayoutParams#WRAP_CONTENT}. */ private static boolean isValid(int actual, int min, int max) { if (actual == ViewGroup.LayoutParams.WRAP_CONTENT) { return true; } return actual >= min && actual <= max; } private synchronized InlineContentCallbackImpl getInlineContentCallback(Context context, Executor callbackExecutor, Consumer callback, InlineTooltipUi inlineTooltipUi) { if (mInlineContentCallback != null) { throw new IllegalStateException("Already called #inflate()"); } return new InlineContentCallbackImpl(context, mContentProvider, callbackExecutor, callback, inlineTooltipUi); } /** * A wrapper class around the {@link InlineContentCallbackImpl} to ensure it's not strongly * reference by the remote system server process. */ private static final class InlineContentCallbackWrapper extends IInlineContentCallback.Stub { private final WeakReference mCallbackImpl; InlineContentCallbackWrapper(InlineContentCallbackImpl callbackImpl) { mCallbackImpl = new WeakReference<>(callbackImpl); } @Override @BinderThread public void onContent(SurfaceControlViewHost.SurfacePackage content, int width, int height) { final InlineContentCallbackImpl callbackImpl = mCallbackImpl.get(); if (callbackImpl != null) { callbackImpl.onContent(content, width, height); } } @Override @BinderThread public void onClick() { final InlineContentCallbackImpl callbackImpl = mCallbackImpl.get(); if (callbackImpl != null) { callbackImpl.onClick(); } } @Override @BinderThread public void onLongClick() { final InlineContentCallbackImpl callbackImpl = mCallbackImpl.get(); if (callbackImpl != null) { callbackImpl.onLongClick(); } } } /** * Handles the communication between the inline suggestion view in current (IME) process and * the remote view provided from the system server. * *

This class is thread safe, because all the outside calls are piped into a single * handler thread to be processed. */ private static final class InlineContentCallbackImpl { @NonNull private final Handler mMainHandler = new Handler(Looper.getMainLooper()); @NonNull private final Context mContext; @Nullable private final IInlineContentProvider mInlineContentProvider; @NonNull private final Executor mCallbackExecutor; /** * Callback from the client (IME) that will receive the inflated suggestion view. It'll * only be called once when the view SurfacePackage is first sent back to the client. Any * updates to the view due to attach to window and detach from window events will be * handled under the hood, transparent from the client. */ @NonNull private final Consumer mCallback; /** * Indicates whether the first content has been received or not. */ private boolean mFirstContentReceived = false; /** * The client (IME) side view which internally wraps a remote view. It'll be set when * {@link #onContent(SurfaceControlViewHost.SurfacePackage, int, int)} is called, which * should only happen once in the lifecycle of this inline suggestion instance. */ @Nullable private InlineContentView mView; /** * The SurfacePackage pointing to the remote view. It's cached here to be sent to the next * available consumer. */ @Nullable private SurfaceControlViewHost.SurfacePackage mSurfacePackage; /** * The callback (from the {@link InlineContentView}) which consumes the surface package. * It's cached here to be called when the SurfacePackage is returned from the remote * view owning process. */ @Nullable private Consumer mSurfacePackageConsumer; @Nullable private InlineTooltipUi mInlineTooltipUi; InlineContentCallbackImpl(@NonNull Context context, @Nullable IInlineContentProvider inlineContentProvider, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull Consumer callback, @Nullable InlineTooltipUi inlineTooltipUi) { mContext = context; mInlineContentProvider = inlineContentProvider; mCallbackExecutor = callbackExecutor; mCallback = callback; mInlineTooltipUi = inlineTooltipUi; } @BinderThread public void onContent(SurfaceControlViewHost.SurfacePackage content, int width, int height) { mMainHandler.post(() -> handleOnContent(content, width, height)); } @MainThread private void handleOnContent(SurfaceControlViewHost.SurfacePackage content, int width, int height) { if (!mFirstContentReceived) { handleOnFirstContentReceived(content, width, height); mFirstContentReceived = true; } else { handleOnSurfacePackage(content); } } /** * Called when the view content is returned for the first time. */ @MainThread private void handleOnFirstContentReceived(SurfaceControlViewHost.SurfacePackage content, int width, int height) { mSurfacePackage = content; if (mSurfacePackage == null) { 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)); } } /** * Called when any subsequent SurfacePackage is returned from the remote view owning * process. */ @MainThread private void handleOnSurfacePackage(SurfaceControlViewHost.SurfacePackage surfacePackage) { if (surfacePackage == null) { return; } if (mSurfacePackage != null || mSurfacePackageConsumer == null) { // The surface package is not consumed, release it immediately. surfacePackage.release(); try { mInlineContentProvider.onSurfacePackageReleased(); } catch (RemoteException e) { Slog.w(TAG, "Error calling onSurfacePackageReleased(): " + e); } return; } mSurfacePackage = surfacePackage; if (mSurfacePackage == null) { return; } if (mSurfacePackageConsumer != null) { mSurfacePackageConsumer.accept(mSurfacePackage); mSurfacePackageConsumer = null; } } @MainThread private void handleOnSurfacePackageReleased() { if (mSurfacePackage != null) { try { mInlineContentProvider.onSurfacePackageReleased(); } catch (RemoteException e) { Slog.w(TAG, "Error calling onSurfacePackageReleased(): " + e); } mSurfacePackage = null; } // Clear the pending surface package consumer, if any. This can happen if the IME // attaches the view to window and then quickly detaches it from the window, before // the surface package requested upon attaching to window was returned. mSurfacePackageConsumer = null; } @MainThread private void handleGetSurfacePackage( Consumer consumer) { if (mSurfacePackage != null) { consumer.accept(mSurfacePackage); } else { mSurfacePackageConsumer = consumer; try { mInlineContentProvider.requestSurfacePackage(); } catch (RemoteException e) { Slog.w(TAG, "Error calling getSurfacePackage(): " + e); consumer.accept(null); mSurfacePackageConsumer = null; } } } private InlineContentView.SurfacePackageUpdater getSurfacePackageUpdater() { return new InlineContentView.SurfacePackageUpdater() { @Override public void onSurfacePackageReleased() { mMainHandler.post( () -> InlineContentCallbackImpl.this.handleOnSurfacePackageReleased()); } @Override public void getSurfacePackage( Consumer consumer) { mMainHandler.post( () -> InlineContentCallbackImpl.this.handleGetSurfacePackage(consumer)); } }; } @BinderThread public void onClick() { mMainHandler.post(() -> { if (mView != null && mView.hasOnClickListeners()) { mView.callOnClick(); } }); } @BinderThread public void onLongClick() { mMainHandler.post(() -> { if (mView != null && mView.hasOnLongClickListeners()) { mView.performLongClick(); } }); } } /** * 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 InlineContentCallbackImplParceling implements Parcelling { @Override public void parcel(InlineContentCallbackImpl item, Parcel dest, int parcelFlags) { } @Override public InlineContentCallbackImpl unparcel(Parcel source) { return null; } } /** * 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.22. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code // // To regenerate run: // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control //@formatter:off /** * Creates a new InlineSuggestion. * * @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 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 } @DataClass.Generated.Member public @NonNull InlineSuggestionInfo getInfo() { return mInfo; } /** * @hide */ @DataClass.Generated.Member public @Nullable IInlineContentProvider getContentProvider() { return mContentProvider; } /** * Used to keep a strong reference to the callback so it doesn't get garbage collected. * * @hide */ @DataClass.Generated.Member public @Nullable InlineContentCallbackImpl getInlineContentCallback() { 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() { // You can override field toString logic by defining methods like: // String fieldNameToString() { ... } return "InlineSuggestion { " + "info = " + mInfo + ", " + "contentProvider = " + mContentProvider + ", " + "inlineContentCallback = " + mInlineContentCallback + ", " + "inlineTooltipUi = " + mInlineTooltipUi + " }"; } @Override @DataClass.Generated.Member public boolean equals(@Nullable Object o) { // You can override field equality logic by defining either of the methods like: // boolean fieldNameEquals(InlineSuggestion other) { ... } // boolean fieldNameEquals(FieldType otherValue) { ... } if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @SuppressWarnings("unchecked") InlineSuggestion that = (InlineSuggestion) o; //noinspection PointlessBooleanExpression 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(mInlineTooltipUi, that.mInlineTooltipUi); } @Override @DataClass.Generated.Member public int hashCode() { // You can override field hashCode logic by defining methods like: // int fieldNameHashCode() { ... } int _hash = 1; _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; } @DataClass.Generated.Member static Parcelling sParcellingForInlineContentCallback = Parcelling.Cache.get( InlineContentCallbackImplParceling.class); static { if (sParcellingForInlineContentCallback == null) { sParcellingForInlineContentCallback = Parcelling.Cache.put( new InlineContentCallbackImplParceling()); } } @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) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } 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 @DataClass.Generated.Member public int describeContents() { return 0; } /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member /* package-private */ InlineSuggestion(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } byte flg = in.readByte(); 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 } @DataClass.Generated.Member public static final @NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public InlineSuggestion[] newArray(int size) { return new InlineSuggestion[size]; } @Override public InlineSuggestion createFromParcel(@NonNull Parcel in) { return new InlineSuggestion(in); } }; @DataClass.Generated( 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\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]\[email protected](genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)") @Deprecated private void __metadata() {} //@formatter:on // End of generated code }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy