diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index a2cb1d5c6bd2a..0e3bcd1b6842d 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -314,6 +314,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation (int) (startValue.right + fraction * (endValue.right - startValue.right)), (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom))); + /** Logging listener. */ + private WindowInsetsAnimationControlListener mLoggingListener; + /** * The default implementation of listener, to be used by InsetsController and InsetsPolicy to * animate insets. @@ -330,6 +333,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final long mDurationMs; private final boolean mDisable; private final int mFloatingImeBottomInset; + private final WindowInsetsAnimationControlListener mLoggingListener; private final ThreadLocal mSfAnimationHandlerThreadLocal = new ThreadLocal() { @@ -343,7 +347,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks, @InsetsType int requestedTypes, @Behavior int behavior, boolean disable, - int floatingImeBottomInset) { + int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener) { mShow = show; mHasAnimationCallbacks = hasAnimationCallbacks; mRequestedTypes = requestedTypes; @@ -351,12 +355,16 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mDurationMs = calculateDurationMs(); mDisable = disable; mFloatingImeBottomInset = floatingImeBottomInset; + mLoggingListener = loggingListener; } @Override public void onReady(WindowInsetsAnimationController controller, int types) { mController = controller; if (DEBUG) Log.d(TAG, "default animation onReady types: " + types); + if (mLoggingListener != null) { + mLoggingListener.onReady(controller, types); + } if (mDisable) { onAnimationFinish(); @@ -410,6 +418,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public void onFinished(WindowInsetsAnimationController controller) { if (DEBUG) Log.d(TAG, "InternalAnimationControlListener onFinished types:" + Type.toString(mRequestedTypes)); + if (mLoggingListener != null) { + mLoggingListener.onFinished(controller); + } } @Override @@ -420,6 +431,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } if (DEBUG) Log.d(TAG, "InternalAnimationControlListener onCancelled types:" + mRequestedTypes); + if (mLoggingListener != null) { + mLoggingListener.onCancelled(controller); + } } protected Interpolator getInsetsInterpolator() { @@ -1147,6 +1161,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation updateRequestedVisibilities(); } + // TODO(b/242962223): Make this setter restrictive. + @Override + public void setSystemDrivenInsetsAnimationLoggingListener( + @Nullable WindowInsetsAnimationControlListener listener) { + mLoggingListener = listener; + } + /** * @return Pair of (types ready to animate, IME ready to animate). */ @@ -1460,7 +1481,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks(); final InternalAnimationControlListener listener = new InternalAnimationControlListener( show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(), - skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP)); + skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP), + mLoggingListener); // We are about to playing the default animation (show/hide). Passing a null frame indicates // the controlled types should be animated regardless of the frame. diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java index c61baf6fb40ca..3fe9110283a67 100644 --- a/core/java/android/view/PendingInsetsController.java +++ b/core/java/android/view/PendingInsetsController.java @@ -44,6 +44,7 @@ public class PendingInsetsController implements WindowInsetsController { private ArrayList mControllableInsetsChangedListeners = new ArrayList<>(); private int mCaptionInsetsHeight = 0; + private WindowInsetsAnimationControlListener mLoggingListener; @Override public void show(int types) { @@ -176,6 +177,9 @@ public class PendingInsetsController implements WindowInsetsController { controller.addOnControllableInsetsChangedListener( mControllableInsetsChangedListeners.get(i)); } + if (mLoggingListener != null) { + controller.setSystemDrivenInsetsAnimationLoggingListener(mLoggingListener); + } // Reset all state so it doesn't get applied twice just in case mRequests.clear(); @@ -184,7 +188,7 @@ public class PendingInsetsController implements WindowInsetsController { mAppearance = 0; mAppearanceMask = 0; mAnimationsDisabled = false; - + mLoggingListener = null; // After replaying, we forward everything directly to the replayed instance. mReplayedInsetsController = controller; } @@ -197,6 +201,16 @@ public class PendingInsetsController implements WindowInsetsController { mReplayedInsetsController = null; } + @Override + public void setSystemDrivenInsetsAnimationLoggingListener( + @Nullable WindowInsetsAnimationControlListener listener) { + if (mReplayedInsetsController != null) { + mReplayedInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener); + } else { + mLoggingListener = listener; + } + } + @Override public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis, @Nullable Interpolator interpolator, diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index 227b9f402bba9..63f9e13214ff0 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -200,6 +200,21 @@ public interface WindowInsetsController { @Nullable CancellationSignal cancellationSignal, @NonNull WindowInsetsAnimationControlListener listener); + /** + * Lets the application add non-controllable listener object that can be called back + * when animation is invoked by the system by host calling methods such as {@link #show} or + * {@link #hide}. + * + * The listener is supposed to be used for logging only, using the control or + * relying on the timing of the callback in any other way is not supported. + * + * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when + * the animation is driven by the system and not the host + * @hide + */ + void setSystemDrivenInsetsAnimationLoggingListener( + @Nullable WindowInsetsAnimationControlListener listener); + /** * Controls the appearance of system bars. *

diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index ed6a649021a13..6e59b83810e1e 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -235,6 +235,21 @@ public class InsetsControllerTest { verify(controlListener, never()).onReady(any(), anyInt()); } + @Test + public void testSystemDrivenInsetsAnimationLoggingListener_onReady() { + prepareControls(); + // only the original thread that created view hierarchy can touch its views + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + WindowInsetsAnimationControlListener loggingListener = + mock(WindowInsetsAnimationControlListener.class); + mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener); + mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true); + // since there is no focused view, forcefully make IME visible. + mController.show(Type.ime(), true /* fromIme */); + verify(loggingListener).onReady(notNull(), anyInt()); + }); + } + @Test public void testAnimationEndState() { InsetsSourceControl[] controls = prepareControls(); diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java index 03c8b1bcb475d..690b358793885 100644 --- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java @@ -212,6 +212,25 @@ public class PendingInsetsControllerTest { verifyZeroInteractions(secondController); } + @Test + public void testSystemDrivenInsetsAnimationLoggingListener() { + WindowInsetsAnimationControlListener listener = + mock(WindowInsetsAnimationControlListener.class); + mPendingInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener); + mPendingInsetsController.replayAndAttach(mReplayedController); + verify(mReplayedController).setSystemDrivenInsetsAnimationLoggingListener(eq(listener)); + } + + @Test + public void testSystemDrivenInsetsAnimationLoggingListener_direct() { + mPendingInsetsController.replayAndAttach(mReplayedController); + WindowInsetsAnimationControlListener listener = + mock(WindowInsetsAnimationControlListener.class); + mPendingInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener); + verify(mReplayedController).setSystemDrivenInsetsAnimationLoggingListener( + eq(listener)); + } + @Test public void testDetachReattach() { mPendingInsetsController.show(systemBars()); diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 3e2d7c9289361..506396a932feb 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -759,7 +759,7 @@ class InsetsPolicy { InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) { super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE, - false /* disable */, 0 /* floatingImeBottomInsets */); + false /* disable */, 0 /* floatingImeBottomInsets */, null); mFinishCallback = finishCallback; mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this); }