diff options
author | 2020-03-23 19:10:03 +0000 | |
---|---|---|
committer | 2020-03-23 19:10:03 +0000 | |
commit | 09b95708628b34df20b219c15c1e60f4bcca481b (patch) | |
tree | 6e4c4ffbe7c1034fe84528d4ffb552b68207daf9 | |
parent | abdeaee816eb2742b572565b4d512d37ff3c312e (diff) | |
parent | a57dadde241a77f390c79b845a6990a9514d8fc8 (diff) |
Merge "Add APIs to move suggestions below/above window" into rvc-dev
-rw-r--r-- | api/current.txt | 15 | ||||
-rw-r--r-- | core/java/android/service/autofill/InlineSuggestionRoot.java | 4 | ||||
-rw-r--r-- | core/java/android/view/SurfaceView.java | 97 | ||||
-rw-r--r-- | core/java/android/view/inline/InlineContentView.java | 187 | ||||
-rw-r--r-- | core/java/android/view/inputmethod/InlineSuggestion.java | 24 |
5 files changed, 300 insertions, 27 deletions
diff --git a/api/current.txt b/api/current.txt index eb8ee3cc9942..c19a70c5c250 100644 --- a/api/current.txt +++ b/api/current.txt @@ -56772,6 +56772,19 @@ package android.view.contentcapture { package android.view.inline { + public class InlineContentView extends android.view.ViewGroup { + method @Nullable public android.view.SurfaceControl getSurfaceControl(); + method public boolean isZOrderedOnTop(); + method public void onLayout(boolean, int, int, int, int); + method public void setSurfaceControlCallback(@Nullable android.view.inline.InlineContentView.SurfaceControlCallback); + method public boolean setZOrderedOnTop(boolean); + } + + public static interface InlineContentView.SurfaceControlCallback { + method public void onCreated(@NonNull android.view.SurfaceControl); + method public void onDestroyed(@NonNull android.view.SurfaceControl); + } + public final class InlinePresentationSpec implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.util.Size getMaxSize(); @@ -56960,7 +56973,7 @@ package android.view.inputmethod { public final class InlineSuggestion implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.view.inputmethod.InlineSuggestionInfo getInfo(); - method public void inflate(@NonNull android.content.Context, @NonNull android.util.Size, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.View>); + method public void inflate(@NonNull android.content.Context, @NonNull android.util.Size, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.inline.InlineContentView>); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InlineSuggestion> CREATOR; } diff --git a/core/java/android/service/autofill/InlineSuggestionRoot.java b/core/java/android/service/autofill/InlineSuggestionRoot.java index 6c9d36b329ff..653e513f13d8 100644 --- a/core/java/android/service/autofill/InlineSuggestionRoot.java +++ b/core/java/android/service/autofill/InlineSuggestionRoot.java @@ -68,7 +68,9 @@ public class InlineSuggestionRoot extends FrameLayout { case MotionEvent.ACTION_MOVE: { final float distance = MathUtils.dist(mDownX, mDownY, event.getX(), event.getY()); - if (distance > mTouchSlop) { + final boolean isSecure = (event.getFlags() + & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) == 0; + if (!isSecure || distance > mTouchSlop) { try { mCallback.onTransferTouchFocusToImeWindow(getViewRootImpl().getInputToken(), getContext().getDisplayId()); diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 3e1e3939d570..59fc6e9b5ede 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -686,16 +686,107 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * SurfaceView is in will be visible on top of its surface. * * <p>Note that this must be set before the surface view's containing - * window is attached to the window manager. + * window is attached to the window manager. If you target {@link Build.VERSION_CODES#R} + * the Z ordering can be changed dynamically if the backing surface is + * created, otherwise it would be applied at surface construction time. * * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}. + * + * @param onTop Whether to show the surface on top of this view's window. */ public void setZOrderOnTop(boolean onTop) { + // In R and above we allow dynamic layer changes. + final boolean allowDynamicChange = getContext().getApplicationInfo().targetSdkVersion + > Build.VERSION_CODES.Q; + setZOrderedOnTop(onTop, allowDynamicChange); + } + + /** + * @return Whether the surface backing this view appears on top of its parent. + * + * @hide + */ + public boolean isZOrderedOnTop() { + return mSubLayer > 0; + } + + /** + * Controls whether the surface view's surface is placed on top of its + * window. Normally it is placed behind the window, to allow it to + * (for the most part) appear to composite with the views in the + * hierarchy. By setting this, you cause it to be placed above the + * window. This means that none of the contents of the window this + * SurfaceView is in will be visible on top of its surface. + * + * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}. + * + * @param onTop Whether to show the surface on top of this view's window. + * @param allowDynamicChange Whether this can happen after the surface is created. + * @return Whether the Z ordering changed. + * + * @hide + */ + public boolean setZOrderedOnTop(boolean onTop, boolean allowDynamicChange) { + final int subLayer; if (onTop) { - mSubLayer = APPLICATION_PANEL_SUBLAYER; + subLayer = APPLICATION_PANEL_SUBLAYER; } else { - mSubLayer = APPLICATION_MEDIA_SUBLAYER; + subLayer = APPLICATION_MEDIA_SUBLAYER; + } + if (mSubLayer == subLayer) { + return false; + } + mSubLayer = subLayer; + + if (!allowDynamicChange) { + return false; + } + if (mSurfaceControl == null) { + return true; + } + final ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot == null) { + return true; } + final Surface parent = viewRoot.mSurface; + if (parent == null || !parent.isValid()) { + return true; + } + + /* + * Schedule a callback that reflects an alpha value onto the underlying surfaces. + * This gets called on a RenderThread worker thread, so members accessed here must + * be protected by a lock. + */ + final boolean useBLAST = viewRoot.useBLAST(); + viewRoot.registerRtFrameCallback(frame -> { + try { + final SurfaceControl.Transaction t = useBLAST + ? viewRoot.getBLASTSyncTransaction() + : new SurfaceControl.Transaction(); + synchronized (mSurfaceControlLock) { + if (!parent.isValid() || mSurfaceControl == null) { + return; + } + updateRelativeZ(t); + if (!useBLAST) { + t.deferTransactionUntil(mSurfaceControl, + viewRoot.getRenderSurfaceControl(), frame); + } + } + // It's possible that mSurfaceControl is released in the UI thread before + // the transaction completes. If that happens, an exception is thrown, which + // must be caught immediately. + t.apply(); + } catch (Exception e) { + Log.e(TAG, System.identityHashCode(this) + + "setZOrderOnTop RT: Exception during surface transaction", e); + } + }); + + invalidate(); + + return true; } /** diff --git a/core/java/android/view/inline/InlineContentView.java b/core/java/android/view/inline/InlineContentView.java index 2a8ca0b6a354..df5bc2fb0cb4 100644 --- a/core/java/android/view/inline/InlineContentView.java +++ b/core/java/android/view/inline/InlineContentView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,22 +17,189 @@ package android.view.inline; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.graphics.PixelFormat; +import android.util.AttributeSet; +import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; +import android.view.SurfaceHolder; import android.view.SurfaceView; +import android.view.ViewGroup; /** - * This class represents a view that can hold an opaque content that may be from a different source. + * This class represents a view that holds opaque content from another app that + * you can inline in your UI. * - * @hide + * <p>Since the content presented by this view is from another security domain,it is + * shown on a remote surface preventing the host application from accessing that content. + * Also the host application cannot interact with the inlined content by injecting touch + * events or clicking programmatically. + * + * <p>This view can be overlaid by other windows, i.e. redressed, but if this is the case + * the inined UI would not be interactive. Sometimes this is desirable, e.g. animating + * transitions. + * + * <p>By default the surface backing this view is shown on top of the hosting window such + * that the inlined content is interactive. However, you can temporarily move the surface + * under the hosting window which could be useful in some cases, e.g. animating transitions. + * At this point the inlined content will not be interactive and the touch events would + * be delivered to your app. */ -public class InlineContentView extends SurfaceView { - public InlineContentView(@NonNull Context context, - @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) { - super(context); - setZOrderOnTop(true); - setChildSurfacePackage(surfacePackage); - getHolder().setFormat(PixelFormat.TRANSPARENT); +public class InlineContentView extends ViewGroup { + + /** + * Callback for observing the lifecycle of the surface control + * that manipulates the backing secure embedded UI surface. + */ + public interface SurfaceControlCallback { + /** + * Called when the backing surface is being created. + * + * @param surfaceControl The surface control to manipulate the surface. + */ + void onCreated(@NonNull SurfaceControl surfaceControl); + + /** + * Called when the backing surface is being destroyed. + * + * @param surfaceControl The surface control to manipulate the surface. + */ + void onDestroyed(@NonNull SurfaceControl surfaceControl); + } + + private final @NonNull SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() { + @Override + public void surfaceCreated(@NonNull SurfaceHolder holder) { + mSurfaceControlCallback.onCreated(mSurfaceView.getSurfaceControl()); + } + + @Override + public void surfaceChanged(@NonNull SurfaceHolder holder, + int format, int width, int height) { + /* do nothing */ + } + + @Override + public void surfaceDestroyed(@NonNull SurfaceHolder holder) { + mSurfaceControlCallback.onDestroyed(mSurfaceView.getSurfaceControl()); + } + }; + + private final @NonNull SurfaceView mSurfaceView; + + private @Nullable SurfaceControlCallback mSurfaceControlCallback; + + /** + * @inheritDoc + * + * @hide + */ + public InlineContentView(@NonNull Context context) { + this(context, null); + } + + /** + * @inheritDoc + * + * @hide + */ + public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * @inheritDoc + * + * @hide + */ + public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + /** + * Gets the surface control. If the surface is not created this method + * returns {@code null}. + * + * @return The surface control. + * + * @see #setSurfaceControlCallback(SurfaceControlCallback) + */ + public @Nullable SurfaceControl getSurfaceControl() { + return mSurfaceView.getSurfaceControl(); + } + + /** + * @inheritDoc + * + * @hide + */ + public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mSurfaceView = new SurfaceView(context, attrs, defStyleAttr, defStyleRes); + mSurfaceView.setZOrderOnTop(true); + mSurfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT); + addView(mSurfaceView); + } + + /** + * Sets the embedded UI. + * @param surfacePackage The embedded UI. + * + * @hide + */ + public void setChildSurfacePackage( + @Nullable SurfaceControlViewHost.SurfacePackage surfacePackage) { + mSurfaceView.setChildSurfacePackage(surfacePackage); + } + + @Override + public void onLayout(boolean changed, int l, int t, int r, int b) { + mSurfaceView.layout(l, t, r, b); + } + + /** + * Sets a callback to observe the lifecycle of the surface control for + * managing the backing surface. + * + * @param callback The callback to set or {@code null} to clear. + */ + public void setSurfaceControlCallback(@Nullable SurfaceControlCallback callback) { + if (mSurfaceControlCallback != null) { + mSurfaceView.getHolder().removeCallback(mSurfaceCallback); + } + mSurfaceControlCallback = callback; + if (mSurfaceControlCallback != null) { + mSurfaceView.getHolder().addCallback(mSurfaceCallback); + } + } + + /** + * @return Whether the surface backing this view appears on top of its parent. + * + * @see #setZOrderedOnTop(boolean) + */ + public boolean isZOrderedOnTop() { + return mSurfaceView.isZOrderedOnTop(); + } + + /** + * Controls whether the backing surface is placed on top of this view's window. + * Normally, it is placed on top of the window, to allow interaction + * with the inlined UI. Via this method, you can place the surface below the + * window. This means that all of the contents of the window this view is in + * will be visible on top of its surface. + * + * <p> The Z ordering can be changed dynamically if the backing surface is + * created, otherwise the ordering would be applied at surface construction time. + * + * @param onTop Whether to show the surface on top of this view's window. + * + * @see #isZOrderedOnTop() + */ + public boolean setZOrderedOnTop(boolean onTop) { + return mSurfaceView.setZOrderedOnTop(onTop, /*allowDynamicChange*/ true); } } diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java index dd1738a5ff29..ab8f36d85400 100644 --- a/core/java/android/view/inputmethod/InlineSuggestion.java +++ b/core/java/android/view/inputmethod/InlineSuggestion.java @@ -29,7 +29,6 @@ import android.os.RemoteException; import android.util.Size; import android.util.Slog; import android.view.SurfaceControlViewHost; -import android.view.View; import android.view.inline.InlineContentView; import android.view.inline.InlinePresentationSpec; @@ -94,15 +93,15 @@ public final class InlineSuggestion implements Parcelable { this(info, contentProvider, /* inlineContentCallback */ null); } - /** * Inflates a view with the content of this suggestion at a specific size. * The size must be between the {@link InlinePresentationSpec#getMinSize() min size} * and the {@link InlinePresentationSpec#getMaxSize() max size} of the presentation * spec returned by {@link InlineSuggestionInfo#getPresentationSpec()}. * - * <p> The caller can attach an {@link View.OnClickListener} and/or an - * {@link View.OnLongClickListener} to the view in the {@code callback} to receive click and + * <p> 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. @@ -113,7 +112,7 @@ public final class InlineSuggestion implements Parcelable { */ public void inflate(@NonNull Context context, @NonNull Size size, @NonNull @CallbackExecutor Executor callbackExecutor, - @NonNull Consumer<View> callback) { + @NonNull Consumer<InlineContentView> callback) { final Size minSize = mInfo.getPresentationSpec().getMinSize(); final Size maxSize = mInfo.getPresentationSpec().getMaxSize(); if (size.getHeight() < minSize.getHeight() || size.getHeight() > maxSize.getHeight() @@ -138,7 +137,7 @@ public final class InlineSuggestion implements Parcelable { } private synchronized InlineContentCallbackImpl getInlineContentCallback(Context context, - Executor callbackExecutor, Consumer<View> callback) { + Executor callbackExecutor, Consumer<InlineContentView> callback) { if (mInlineContentCallback != null) { throw new IllegalStateException("Already called #inflate()"); } @@ -185,12 +184,12 @@ public final class InlineSuggestion implements Parcelable { private final @NonNull Context mContext; private final @NonNull Executor mCallbackExecutor; - private final @NonNull Consumer<View> mCallback; - private @Nullable View mView; + private final @NonNull Consumer<InlineContentView> mCallback; + private @Nullable InlineContentView mView; InlineContentCallbackImpl(@NonNull Context context, @NonNull @CallbackExecutor Executor callbackExecutor, - @NonNull Consumer<View> callback) { + @NonNull Consumer<InlineContentView> callback) { mContext = context; mCallbackExecutor = callbackExecutor; mCallback = callback; @@ -201,7 +200,8 @@ public final class InlineSuggestion implements Parcelable { if (content == null) { mCallbackExecutor.execute(() -> mCallback.accept(/* view */null)); } else { - mView = new InlineContentView(mContext, content); + mView = new InlineContentView(mContext); + mView.setChildSurfacePackage(content); mCallbackExecutor.execute(() -> mCallback.accept(mView)); } } @@ -398,10 +398,10 @@ public final class InlineSuggestion implements Parcelable { }; @DataClass.Generated( - time = 1583889058241L, + time = 1584679775946L, codegenVersion = "1.0.15", 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<android.view.View>)\nprivate synchronized android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\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\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<android.view.inline.InlineContentView>)\nprivate synchronized android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer<android.view.inline.InlineContentView>)\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() {} |