Merge changes I88f7f7e5,If6c1153d

* changes:
  Expand docs for animation listener
  A brave new world for window insets (11/n)
This commit is contained in:
Jorim Jaggi
2019-01-14 15:37:23 +00:00
committed by Android (Google) Code Review
9 changed files with 310 additions and 24 deletions

View File

@@ -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.
* <p>
* Both input and animation may change insets, so we need to run this after these callbacks, but
* before traversals.
* <p>
* 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);

View File

@@ -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<InsetsSourceConsumer> mConsumers;
@@ -61,19 +63,23 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
private final InsetsState mInitialInsetsState;
private final @InsetType int mTypes;
private final Supplier<SyncRtSurfaceTransactionApplier> mTransactionApplierSupplier;
private final InsetsController mController;
private final WindowInsetsAnimationListener.InsetsAnimation mAnimation;
private Insets mCurrentInsets;
private Insets mPendingInsets;
@VisibleForTesting
public InsetsAnimationControlImpl(SparseArray<InsetsSourceConsumer> consumers, Rect frame,
InsetsState state, WindowInsetsAnimationControlListener listener,
@InsetType int types,
Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier) {
Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier,
InsetsController controller) {
mConsumers = consumers;
mListener = listener;
mTypes = types;
mTransactionApplierSupplier = transactionApplierSupplier;
mInitialInsetsState = new InsetsState(state);
mController = controller;
mInitialInsetsState = new InsetsState(state, true /* copySources */);
mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */);
mHiddenInsets = calculateInsets(mInitialInsetsState, frame, consumers, false /* shown */,
null /* typeSideMap */);
@@ -83,6 +89,10 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
// TODO: Check for controllability first and wait for IME if needed.
listener.onReady(this, types);
mAnimation = new WindowInsetsAnimationListener.InsetsAnimation(mTypes, mHiddenInsets,
mShownInsets);
mController.dispatchAnimationStarted(mAnimation);
}
@Override
@@ -108,29 +118,35 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
@Override
public void changeInsets(Insets insets) {
insets = sanitize(insets);
final Insets offset = Insets.subtract(mShownInsets, insets);
mPendingInsets = sanitize(insets);
mController.scheduleApplyChangeInsets();
}
void applyChangeInsets(InsetsState state) {
final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
ArrayList<SurfaceParams> params = new ArrayList<>();
if (offset.left != 0) {
updateLeashesForSide(INSET_SIDE_LEFT, offset.left, params);
updateLeashesForSide(INSET_SIDE_LEFT, offset.left, params, state);
}
if (offset.top != 0) {
updateLeashesForSide(INSET_SIDE_TOP, offset.top, params);
updateLeashesForSide(INSET_SIDE_TOP, offset.top, params, state);
}
if (offset.right != 0) {
updateLeashesForSide(INSET_SIDE_RIGHT, offset.right, params);
updateLeashesForSide(INSET_SIDE_RIGHT, offset.right, params, state);
}
if (offset.bottom != 0) {
updateLeashesForSide(INSET_SIDE_BOTTOM, offset.bottom, params);
updateLeashesForSide(INSET_SIDE_BOTTOM, offset.bottom, params, state);
}
SyncRtSurfaceTransactionApplier applier = mTransactionApplierSupplier.get();
applier.scheduleApply(params.toArray(new SurfaceParams[params.size()]));
mCurrentInsets = insets;
mCurrentInsets = mPendingInsets;
}
@Override
public void finish(int shownTypes) {
// TODO
mController.dispatchAnimationFinished(mAnimation);
}
private Insets calculateInsets(InsetsState state, Rect frame,
@@ -146,7 +162,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
@Nullable @InsetSide SparseIntArray typeSideMap) {
return state.calculateInsets(frame, false /* isScreenRound */,
false /* alwaysConsumerNavBar */, null /* displayCutout */, typeSideMap)
.getSystemWindowInsets();
.getInsets(mTypes);
}
private Insets sanitize(Insets insets) {
@@ -154,7 +170,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
}
private void updateLeashesForSide(@InsetSide int side, int inset,
ArrayList<SurfaceParams> surfaceParams) {
ArrayList<SurfaceParams> surfaceParams, InsetsState state) {
ArraySet<InsetsSourceConsumer> items = mSideSourceMap.get(side);
// TODO: Implement behavior when inset spans over multiple types
for (int i = items.size() - 1; i >= 0; i--) {
@@ -162,24 +178,32 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
final InsetsSource source = mInitialInsetsState.getSource(consumer.getType());
final SurfaceControl leash = consumer.getControl().getLeash();
mTmpMatrix.setTranslate(source.getFrame().left, source.getFrame().top);
addTranslationToMatrix(side, inset, mTmpMatrix);
mTmpFrame.set(source.getFrame());
addTranslationToMatrix(side, inset, mTmpMatrix, mTmpFrame);
state.getSource(source.getType()).setFrame(mTmpFrame);
surfaceParams.add(new SurfaceParams(leash, 1f, mTmpMatrix, null, 0, 0f));
}
}
private void addTranslationToMatrix(@InsetSide int side, int inset, Matrix m) {
private void addTranslationToMatrix(@InsetSide int side, int inset, Matrix m, Rect frame) {
switch (side) {
case INSET_SIDE_LEFT:
m.postTranslate(-inset, 0);
frame.offset(-inset, 0);
break;
case INSET_SIDE_TOP:
m.postTranslate(0, -inset);
frame.offset(0, -inset);
break;
case INSET_SIDE_RIGHT:
m.postTranslate(inset, 0);
frame.offset(inset, 0);
break;
case INSET_SIDE_BOTTOM:
m.postTranslate(0, inset);
frame.offset(0, inset);
break;
}
}

View File

@@ -18,6 +18,7 @@ package android.view;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.RemoteException;
import android.util.ArraySet;
@@ -49,9 +50,29 @@ public class InsetsController implements WindowInsetsController {
private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>();
private WindowInsets mLastInsets;
private boolean mAnimCallbackScheduled;
private final Runnable mAnimCallback;
public InsetsController(ViewRootImpl viewRoot) {
mViewRoot = viewRoot;
mAnimCallback = () -> {
mAnimCallbackScheduled = false;
if (mAnimationControls.isEmpty()) {
return;
}
InsetsState state = new InsetsState(mState, true /* copySources */);
for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
mAnimationControls.get(i).applyChangeInsets(state);
}
WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(),
mLastInsets.shouldAlwaysConsumeNavBar(), mLastInsets.getDisplayCutout(),
null /* typeSideMap */);
mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets);
};
}
void onFrameChanged(Rect frame) {
@@ -82,8 +103,9 @@ public class InsetsController implements WindowInsetsController {
@VisibleForTesting
public WindowInsets calculateInsets(boolean isScreenRound,
boolean alwaysConsumeNavBar, DisplayCutout cutout) {
return mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout,
mLastInsets = mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout,
null /* typeSideMap */);
return mLastInsets;
}
/**
@@ -148,7 +170,7 @@ public class InsetsController implements WindowInsetsController {
}
final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
mFrame, mState, listener, types,
() -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView));
() -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this);
mAnimationControls.add(controller);
}
@@ -200,4 +222,20 @@ public class InsetsController implements WindowInsetsController {
pw.println(prefix); pw.println("InsetsController:");
mState.dump(prefix + " ", pw);
}
void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation);
}
void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
}
void scheduleApplyChangeInsets() {
if (!mAnimCallbackScheduled) {
mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
mAnimCallback, null /* token*/);
mAnimCallbackScheduled = true;
}
}
}

View File

@@ -107,6 +107,10 @@ public class InsetsState implements Parcelable {
set(copy);
}
public InsetsState(InsetsState copy, boolean copySources) {
set(copy, copySources);
}
/**
* Calculates {@link WindowInsets} based on the current source configuration.
*

View File

@@ -96,6 +96,7 @@ import android.view.AccessibilityIterators.ParagraphTextSegmentIterator;
import android.view.AccessibilityIterators.TextSegmentIterator;
import android.view.AccessibilityIterators.WordTextSegmentIterator;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.WindowInsetsAnimationListener.InsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityEventSource;
import android.view.accessibility.AccessibilityManager;
@@ -4547,6 +4548,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
OnCapturedPointerListener mOnCapturedPointerListener;
private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
private WindowInsetsAnimationListener mWindowInsetsAnimationListener;
}
@UnsupportedAppUsage
@@ -10487,6 +10490,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
/**
* Sets a {@link WindowInsetsAnimationListener} to be notified about animations of windows that
* cause insets.
*
* @param listener The listener to set.
* @hide pending unhide
*/
public void setWindowInsetsAnimationListener(WindowInsetsAnimationListener listener) {
getListenerInfo().mWindowInsetsAnimationListener = listener;
}
void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) {
if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
mListenerInfo.mWindowInsetsAnimationListener.onStarted(animation);
}
}
WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) {
if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
return mListenerInfo.mWindowInsetsAnimationListener.onProgress(insets);
} else {
return insets;
}
}
void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
mListenerInfo.mWindowInsetsAnimationListener.onFinished(animation);
}
}
/**
* Compute the view's coordinate within the surface.
*

View File

@@ -51,6 +51,7 @@ import android.util.Pools;
import android.util.Pools.SynchronizedPool;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.WindowInsetsAnimationListener.InsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -7139,6 +7140,34 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return insets;
}
@Override
void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) {
super.dispatchWindowInsetsAnimationStarted(animation);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).dispatchWindowInsetsAnimationStarted(animation);
}
}
@Override
WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) {
insets = super.dispatchWindowInsetsAnimationProgress(insets);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).dispatchWindowInsetsAnimationProgress(insets);
}
return insets;
}
@Override
void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
super.dispatchWindowInsetsAnimationFinished(animation);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).dispatchWindowInsetsAnimationFinished(animation);
}
}
/**
* Returns the animation listener to which layout animation events are
* sent.

View File

@@ -19,6 +19,7 @@ package android.view;
import android.annotation.NonNull;
import android.graphics.Insets;
import android.view.WindowInsets.Type.InsetType;
import android.view.WindowInsetsAnimationListener.InsetsAnimation;
/**
* Interface to control a window inset animation frame-by-frame.
@@ -28,8 +29,13 @@ public interface WindowInsetsAnimationController {
/**
* Retrieves the {@link Insets} when the windows this animation is controlling are fully hidden.
* <p>
* 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 {
* <p>
* 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.
* <p>
* 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 {
* <p>
* Note that this will <b>not</b> 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.
* <p>
* {@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);

View File

@@ -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.
* <p>
* 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}.
* <p>
* However, if the size of a window that causes insets is changing, these are the
* lower/upper bounds of that size animation.
* <p>
* 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;
}
}
}

View File

@@ -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