diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 96ef8ba1a2412..ccd0fc179f0e7 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -22,6 +22,7 @@ import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.graphics.FrameInfo; +import android.graphics.Insets; import android.hardware.display.DisplayManagerGlobal; import android.os.Build; import android.os.Handler; @@ -199,7 +200,7 @@ public final class Choreographer { * @hide */ private static final String[] CALLBACK_TRACE_TITLES = { - "input", "animation", "traversal", "commit" + "input", "animation", "insets_animation", "traversal", "commit" }; /** @@ -209,18 +210,33 @@ public final class Choreographer { public static final int CALLBACK_INPUT = 0; /** - * Callback type: Animation callback. Runs before traversals. + * Callback type: Animation callback. Runs before {@link #CALLBACK_INSETS_ANIMATION}. * @hide */ @TestApi public static final int CALLBACK_ANIMATION = 1; + /** + * Callback type: Animation callback to handle inset updates. This is separate from + * {@link #CALLBACK_ANIMATION} as we need to "gather" all inset animation updates via + * {@link WindowInsetsAnimationController#changeInsets} for multiple ongoing animations but then + * update the whole view system with a single callback to {@link View#dispatchWindowInsetsAnimationProgress} + * that contains all the combined updated insets. + *
+ * Both input and animation may change insets, so we need to run this after these callbacks, but + * before traversals. + *
+ * Runs before traversals.
+ * @hide
+ */
+ public static final int CALLBACK_INSETS_ANIMATION = 2;
+
/**
* Callback type: Traversal callback. Handles layout and draw. Runs
* after all other asynchronous messages have been handled.
* @hide
*/
- public static final int CALLBACK_TRAVERSAL = 2;
+ public static final int CALLBACK_TRAVERSAL = 3;
/**
* Callback type: Commit callback. Handles post-draw operations for the frame.
@@ -232,7 +248,7 @@ public final class Choreographer {
* to the view hierarchy state) actually took effect.
* @hide
*/
- public static final int CALLBACK_COMMIT = 3;
+ public static final int CALLBACK_COMMIT = 4;
private static final int CALLBACK_LAST = CALLBACK_COMMIT;
@@ -704,6 +720,7 @@ public final class Choreographer {
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
+ doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 7b9f78e700500..ce71b07da8051 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -45,7 +45,9 @@ import java.util.function.Supplier;
* @hide
*/
@VisibleForTesting
-public class InsetsAnimationControlImpl implements WindowInsetsAnimationController {
+public class InsetsAnimationControlImpl implements WindowInsetsAnimationController {
+
+ private final Rect mTmpFrame = new Rect();
private final WindowInsetsAnimationControlListener mListener;
private final SparseArray
+ * If there are any animation listeners registered, this value is the same as
+ * {@link InsetsAnimation#getLowerBound()} that will be passed into the callbacks.
*
* @return Insets when the windows this animation is controlling are fully hidden.
+ *
+ * @see InsetsAnimation#getLowerBound()
*/
@NonNull Insets getHiddenStateInsets();
@@ -38,8 +44,13 @@ public interface WindowInsetsAnimationController {
*
* In case the size of a window causing insets is changing in the middle of the animation, we
* execute that height change after this animation has finished.
+ *
+ * If there are any animation listeners registered, this value is the same as
+ * {@link InsetsAnimation#getUpperBound()} that will be passed into the callbacks.
*
* @return Insets when the windows this animation is controlling are fully shown.
+ *
+ * @see InsetsAnimation#getUpperBound()
*/
@NonNull Insets getShownStateInsets();
@@ -59,8 +70,11 @@ public interface WindowInsetsAnimationController {
*
* Note that this will not inform the view system of a full inset change via
* {@link View#dispatchApplyWindowInsets} in order to avoid a full layout pass during the
- * animation. If you'd like to animate views during a window inset animation, use
- * TODO add link to animation listeners.
+ * animation. If you'd like to animate views during a window inset animation, register a
+ * {@link WindowInsetsAnimationListener} by calling
+ * {@link View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)} that will be
+ * notified about any insets change via {@link WindowInsetsAnimationListener#onProgress} during
+ * the animation.
*
* {@link View#dispatchApplyWindowInsets} will instead be called once the animation has
* finished, i.e. once {@link #finish} has been called.
@@ -70,6 +84,9 @@ public interface WindowInsetsAnimationController {
* the resulting insets of that configuration will match the passed in parameter.
* Note that these insets are being clamped to the range from
* {@link #getHiddenStateInsets} to {@link #getShownStateInsets}
+ *
+ * @see WindowInsetsAnimationListener
+ * @see View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)
*/
void changeInsets(@NonNull Insets insets);
diff --git a/core/java/android/view/WindowInsetsAnimationListener.java b/core/java/android/view/WindowInsetsAnimationListener.java
new file mode 100644
index 0000000000000..682ab5bfb63cc
--- /dev/null
+++ b/core/java/android/view/WindowInsetsAnimationListener.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2018 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;
+
+import android.graphics.Insets;
+
+/**
+ * Interface that allows the application to listen to animation events for windows that cause
+ * insets.
+ * @hide pending unhide
+ */
+public interface WindowInsetsAnimationListener {
+
+ /**
+ * Called when an inset animation gets started.
+ *
+ * @param animation The animation that is about to start.
+ */
+ void onStarted(InsetsAnimation animation);
+
+ /**
+ * Called when the insets change as part of running an animation. Note that even if multiple
+ * animations for different types are running, there will only be one progress callback per
+ * frame. The {@code insets} passed as an argument represents the overall state and will include
+ * all types, regardless of whether they are animating or not.
+ *
+ * Note that insets dispatch is hierarchical: It will start at the root of the view hierarchy,
+ * and then traverse it and invoke the callback of the specific {@link View} being traversed.
+ * The callback may return a modified instance by calling {@link WindowInsets#inset(int, int, int, int)}
+ * to indicate that a part of the insets have been used to offset or clip its children, and the
+ * children shouldn't worry about that part anymore.
+ *
+ * @param insets The current insets.
+ * @return The insets to dispatch to the subtree of the hierarchy.
+ */
+ WindowInsets onProgress(WindowInsets insets);
+
+ /**
+ * Called when an inset animation has finished.
+ *
+ * @param animation The animation that has finished running.
+ */
+ void onFinished(InsetsAnimation animation);
+
+ /**
+ * Class representing an animation of a set of windows that cause insets.
+ */
+ class InsetsAnimation {
+
+ private final @WindowInsets.Type.InsetType int mTypeMask;
+ private final Insets mLowerBound;
+ private final Insets mUpperBound;
+
+ /**
+ * @hide
+ */
+ InsetsAnimation(int typeMask, Insets lowerBound, Insets upperBound) {
+ mTypeMask = typeMask;
+ mLowerBound = lowerBound;
+ mUpperBound = upperBound;
+ }
+
+ /**
+ * @return The bitmask of {@link WindowInsets.Type.InsetType}s that are animating.
+ */
+ public @WindowInsets.Type.InsetType int getTypeMask() {
+ return mTypeMask;
+ }
+
+ /**
+ * Queries the lower inset bound of the animation. If the animation is about showing or
+ * hiding a window that cause insets, the lower bound is {@link Insets#NONE} and the upper
+ * bound is the same as {@link WindowInsets#getInsets(int)} for the fully shown state. This
+ * is the same as {@link WindowInsetsAnimationController#getHiddenStateInsets} and
+ * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
+ * invoked because of an animation that originates from
+ * {@link WindowInsetsAnimationController}.
+ *
+ * However, if the size of a window that causes insets is changing, these are the
+ * lower/upper bounds of that size animation.
+ *
+ * There are no overlapping animations for a specific type, but there may be two animations
+ * running at the same time for different inset types.
+ *
+ * @see #getUpperBound()
+ * @see WindowInsetsAnimationController#getHiddenStateInsets
+ * TODO: It's a bit weird that these are global per window but onProgress is hierarchical.
+ * TODO: If multiple types are animating, querying the bound per type isn't possible. Should
+ * we:
+ * 1. Offer bounds by type here?
+ * 2. Restrict one animation to one single type only?
+ * Returning WindowInsets here isn't feasible in case of overlapping animations: We can't
+ * fill in the insets for the types from the other animation into the WindowInsets object
+ * as it's changing as well.
+ */
+ public Insets getLowerBound() {
+ return mLowerBound;
+ }
+
+ /**
+ * @see #getLowerBound()
+ * @see WindowInsetsAnimationController#getShownStateInsets
+ */
+ public Insets getUpperBound() {
+ return mUpperBound;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index d520f151c2fa8..81ca9109c6432 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -19,6 +19,7 @@ package android.view;
import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
import static android.view.InsetsState.TYPE_TOP_BAR;
import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.graphics.Insets;
@@ -83,7 +84,7 @@ public class InsetsAnimationControlImplTest {
consumers.put(TYPE_NAVIGATION_BAR, navConsumer);
mController = new InsetsAnimationControlImpl(consumers,
new Rect(0, 0, 500, 500), state, mMockListener, WindowInsets.Type.systemBars(),
- () -> mMockTransactionApplier);
+ () -> mMockTransactionApplier, mock(InsetsController.class));
}
@Test