From 6d5c801c78093cf9ae70139c4e2a2737efbf2359 Mon Sep 17 00:00:00 2001 From: Jorim Jaggi Date: Fri, 28 Feb 2020 01:40:27 +0100 Subject: [PATCH] Use separate thread if app doesn't listen to animations There is no need to introduce jank risk and run the inset animations on the main thread if the app doesn't listen to animation events. In that case, move the animations onto a separate thread. Bug: 118118435 Test: Inspect systrace Change-Id: Ib6e4b4ce8e9dd8e27761ced6eb8d7700b6236a32 --- .../java/android/animation/ValueAnimator.java | 17 ++- .../view/InsetsAnimationControlCallbacks.java | 7 +- .../view/InsetsAnimationControlImpl.java | 27 ++-- .../view/InsetsAnimationControlRunner.java | 56 ++++++++ .../android/view/InsetsAnimationThread.java | 70 ++++++++++ .../InsetsAnimationThreadControlRunner.java | 130 ++++++++++++++++++ core/java/android/view/InsetsController.java | 129 +++++++++++------ .../android/view/InsetsSourceConsumer.java | 2 +- .../android/view/InsetsSourceControl.java | 6 +- core/java/android/view/View.java | 9 ++ core/java/android/view/ViewGroup.java | 28 ++++ .../android/view/WindowManagerGlobal.java | 8 ++ .../view/InsetsAnimationControlImplTest.java | 4 +- .../android/view/InsetsControllerTest.java | 68 ++++----- .../com/android/server/wm/InsetsPolicy.java | 12 +- 15 files changed, 462 insertions(+), 111 deletions(-) create mode 100644 core/java/android/view/InsetsAnimationControlRunner.java create mode 100644 core/java/android/view/InsetsAnimationThread.java create mode 100644 core/java/android/view/InsetsAnimationThreadControlRunner.java diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index ca37e9b107a03..2c41e8d0925a9 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -18,6 +18,7 @@ package android.animation; import android.annotation.CallSuper; import android.annotation.IntDef; +import android.annotation.Nullable; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; @@ -268,6 +269,11 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio */ private float mDurationScale = -1f; + /** + * Animation handler used to schedule updates for this animation. + */ + private AnimationHandler mAnimationHandler; + /** * Public constants */ @@ -1684,6 +1690,15 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio * @hide */ public AnimationHandler getAnimationHandler() { - return AnimationHandler.getInstance(); + return mAnimationHandler != null ? mAnimationHandler : AnimationHandler.getInstance(); + } + + /** + * Sets the animation handler used to schedule updates for this animator or {@code null} to use + * the default handler. + * @hide + */ + public void setAnimationHandler(@Nullable AnimationHandler animationHandler) { + mAnimationHandler = animationHandler; } } diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java index 5d5edecead226..a15d6c79417c5 100644 --- a/core/java/android/view/InsetsAnimationControlCallbacks.java +++ b/core/java/android/view/InsetsAnimationControlCallbacks.java @@ -16,7 +16,6 @@ package android.view; -import android.view.InsetsController.LayoutInsetsDuringAnimation; import android.view.WindowInsetsAnimation.Bounds; /** @@ -37,7 +36,7 @@ public interface InsetsAnimationControlCallbacks { void startAnimation(InsetsAnimationControlImpl controller, WindowInsetsAnimationControlListener listener, int types, WindowInsetsAnimation animation, - Bounds bounds, @LayoutInsetsDuringAnimation int layoutDuringAnimation); + Bounds bounds); /** * Schedule the apply by posting the animation callback. @@ -46,10 +45,10 @@ public interface InsetsAnimationControlCallbacks { /** * Finish the final steps after the animation. - * @param controller The controller used to control the animation. + * @param runner The runner used to run the animation. * @param shown {@code true} if the insets are shown. */ - void notifyFinished(InsetsAnimationControlImpl controller, boolean shown); + void notifyFinished(InsetsAnimationControlRunner runner, boolean shown); /** * Apply the new params to the surface. diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index ae509f3a82c4b..2b30c2dd658e0 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -31,9 +31,7 @@ import android.util.ArraySet; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.SparseSetArray; -import android.view.InsetsController.LayoutInsetsDuringAnimation; import android.view.InsetsState.InternalInsetsSide; -import android.view.InsetsState.InternalInsetsType; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; @@ -49,7 +47,8 @@ import java.util.ArrayList; * @hide */ @VisibleForTesting -public class InsetsAnimationControlImpl implements WindowInsetsAnimationController { +public class InsetsAnimationControlImpl implements WindowInsetsAnimationController, + InsetsAnimationControlRunner { private final Rect mTmpFrame = new Rect(); @@ -84,8 +83,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll InsetsState state, WindowInsetsAnimationControlListener listener, @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator, - boolean fade, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation, - @AnimationType int animationType) { + boolean fade, @AnimationType int animationType) { mControls = controls; mListener = listener; mTypes = types; @@ -105,7 +103,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mAnimation.setAlpha(getCurrentAlpha()); mAnimationType = animationType; mController.startAnimation(this, listener, types, mAnimation, - new Bounds(mHiddenInsets, mShownInsets), layoutInsetsDuringAnimation); + new Bounds(mHiddenInsets, mShownInsets)); } @Override @@ -133,11 +131,8 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll return mTypes; } - boolean controlsInternalType(@InternalInsetsType int type) { - return InsetsState.toInternalType(mTypes).contains(type); - } - - @AnimationType int getAnimationType() { + @Override + public @AnimationType int getAnimationType() { return mAnimationType; } @@ -205,7 +200,8 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll return mAnimation.getFraction(); } - public void onCancelled() { + @Override + public void cancel() { if (mFinished) { return; } @@ -217,7 +213,8 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll return mCancelled; } - WindowInsetsAnimation getAnimation() { + @Override + public WindowInsetsAnimation getAnimation() { return mAnimation; } @@ -225,6 +222,10 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll return mListener; } + SparseArray getControls() { + return mControls; + } + private Insets calculateInsets(InsetsState state, Rect frame, SparseArray controls, boolean shown, @Nullable @InternalInsetsSide SparseIntArray typeSideMap) { diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java new file mode 100644 index 0000000000000..0711c3e166d83 --- /dev/null +++ b/core/java/android/view/InsetsAnimationControlRunner.java @@ -0,0 +1,56 @@ +/* + * 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. + * 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.view.InsetsController.AnimationType; +import android.view.InsetsState.InternalInsetsType; +import android.view.WindowInsets.Type.InsetsType; + +/** + * Interface representing a runner for an insets animation. + * + * @hide + */ +public interface InsetsAnimationControlRunner { + + /** + * @return The {@link InsetsType} the animation of this runner is controlling. + */ + @InsetsType int getTypes(); + + /** + * Cancels the animation. + */ + void cancel(); + + /** + * @return The animation this runner is running. + */ + WindowInsetsAnimation getAnimation(); + + /** + * @return Whether {@link #getTypes()} maps to a specific {@link InternalInsetsType}. + */ + default boolean controlsInternalType(@InternalInsetsType int type) { + return InsetsState.toInternalType(getTypes()).contains(type); + } + + /** + * @return The animation type this runner is running. + */ + @AnimationType int getAnimationType(); +} diff --git a/core/java/android/view/InsetsAnimationThread.java b/core/java/android/view/InsetsAnimationThread.java new file mode 100644 index 0000000000000..cdf97333a0ca6 --- /dev/null +++ b/core/java/android/view/InsetsAnimationThread.java @@ -0,0 +1,70 @@ +/* + * 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. + * 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.os.Handler; +import android.os.HandlerThread; +import android.os.Trace; + +/** + * Thread to be used for inset animations to be running off the main thread. + * @hide + */ +public class InsetsAnimationThread extends HandlerThread { + + private static InsetsAnimationThread sInstance; + private static Handler sHandler; + + private InsetsAnimationThread() { + // TODO: Should this use higher priority? + super("InsetsAnimations"); + } + + private static void ensureThreadLocked() { + if (sInstance == null) { + sInstance = new InsetsAnimationThread(); + sInstance.start(); + sInstance.getLooper().setTraceTag(Trace.TRACE_TAG_VIEW); + sHandler = new Handler(sInstance.getLooper()); + } + } + + public static void release() { + synchronized (InsetsAnimationThread.class) { + if (sInstance == null) { + return; + } + sInstance.getLooper().quitSafely(); + sInstance = null; + sHandler = null; + } + } + + public static InsetsAnimationThread get() { + synchronized (InsetsAnimationThread.class) { + ensureThreadLocked(); + return sInstance; + } + } + + public static Handler getHandler() { + synchronized (InsetsAnimationThread.class) { + ensureThreadLocked(); + return sHandler; + } + } +} diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java new file mode 100644 index 0000000000000..9c27802293b8f --- /dev/null +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -0,0 +1,130 @@ +/* + * 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. + * 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 static android.view.SyncRtSurfaceTransactionApplier.applyParams; + +import android.annotation.UiThread; +import android.graphics.Rect; +import android.os.Handler; +import android.util.SparseArray; +import android.view.InsetsController.AnimationType; +import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; +import android.view.WindowInsets.Type.InsetsType; +import android.view.WindowInsetsAnimation.Bounds; +import android.view.animation.Interpolator; + +/** + * Insets animation runner that uses {@link InsetsAnimationThread} to run the animation off from the + * main thread. + * + * @hide + */ +public class InsetsAnimationThreadControlRunner implements InsetsAnimationControlRunner { + + private final InsetsAnimationControlImpl mControl; + private final InsetsAnimationControlCallbacks mOuterCallbacks; + private final Handler mMainThreadHandler; + private final InsetsState mState = new InsetsState(); + private final InsetsAnimationControlCallbacks mCallbacks = + new InsetsAnimationControlCallbacks() { + + private final float[] mTmpFloat9 = new float[9]; + + @Override + @UiThread + public void startAnimation(InsetsAnimationControlImpl controller, + WindowInsetsAnimationControlListener listener, int types, + WindowInsetsAnimation animation, Bounds bounds) { + // Animation will be started in constructor already. + } + + @Override + public void scheduleApplyChangeInsets() { + mControl.applyChangeInsets(mState); + } + + @Override + public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) { + releaseControls(mControl.getControls()); + mMainThreadHandler.post(() -> + mOuterCallbacks.notifyFinished(InsetsAnimationThreadControlRunner.this, shown)); + } + + @Override + public void applySurfaceParams(SurfaceParams... params) { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (int i = params.length - 1; i >= 0; i--) { + SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i]; + applyParams(t, surfaceParams, mTmpFloat9); + } + t.apply(); + t.close(); + } + }; + + @UiThread + public InsetsAnimationThreadControlRunner(SparseArray controls, Rect frame, + InsetsState state, WindowInsetsAnimationControlListener listener, + @InsetsType int types, + InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator, + boolean fade, @AnimationType int animationType, Handler mainThreadHandler) { + mMainThreadHandler = mainThreadHandler; + mOuterCallbacks = controller; + mControl = new InsetsAnimationControlImpl(copyControls(controls), frame, state, listener, + types, mCallbacks, durationMs, interpolator, fade, animationType); + InsetsAnimationThread.getHandler().post(() -> listener.onReady(mControl, types)); + } + + private void releaseControls(SparseArray controls) { + for (int i = controls.size() - 1; i >= 0; i--) { + controls.valueAt(i).release(SurfaceControl::release); + } + } + + private SparseArray copyControls( + SparseArray controls) { + SparseArray copy = new SparseArray<>(controls.size()); + for (int i = 0; i < controls.size(); i++) { + copy.append(controls.keyAt(i), new InsetsSourceControl(controls.valueAt(i))); + } + return copy; + } + + @Override + @UiThread + public int getTypes() { + return mControl.getTypes(); + } + + @Override + @UiThread + public void cancel() { + InsetsAnimationThread.getHandler().post(mControl::cancel); + } + + @Override + @UiThread + public WindowInsetsAnimation getAnimation() { + return mControl.getAnimation(); + } + + @Override + public int getAnimationType() { + return mControl.getAnimationType(); + } +} diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 607886f3b13bb..c1763d62d8297 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -23,6 +23,7 @@ import static android.view.WindowInsets.Type.ime; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED; +import android.animation.AnimationHandler; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -53,6 +54,7 @@ import android.view.animation.PathInterpolator; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -167,10 +169,22 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private WindowInsetsAnimationController mController; private ObjectAnimator mAnimator; - protected boolean mShow; + private final boolean mShow; + private final boolean mUseSfVsync; - public InternalAnimationControlListener(boolean show) { + private ThreadLocal mSfAnimationHandlerThreadLocal = + new ThreadLocal() { + @Override + protected AnimationHandler initialValue() { + AnimationHandler handler = new AnimationHandler(); + handler.setProvider(new SfVsyncFrameCallbackProvider()); + return handler; + } + }; + + public InternalAnimationControlListener(boolean show, boolean useSfVsync) { mShow = show; + mUseSfVsync = useSfVsync; } @Override @@ -193,6 +207,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation onAnimationFinish(); } }); + if (mUseSfVsync) { + mAnimator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get()); + } mAnimator.start(); } @@ -228,12 +245,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation */ private static class RunningAnimation { - RunningAnimation(InsetsAnimationControlImpl control, int type) { - this.control = control; + RunningAnimation(InsetsAnimationControlRunner runner, int type) { + this.runner = runner; this.type = type; } - final InsetsAnimationControlImpl control; + final InsetsAnimationControlRunner runner; final @AnimationType int type; /** @@ -252,7 +269,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation PendingControlRequest(@InsetsType int types, WindowInsetsAnimationControlListener listener, long durationMs, Interpolator interpolator, @AnimationType int animationType, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation, - CancellationSignal cancellationSignal) { + CancellationSignal cancellationSignal, boolean useInsetsAnimationThread) { this.types = types; this.listener = listener; this.durationMs = durationMs; @@ -260,6 +277,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation this.animationType = animationType; this.layoutInsetsDuringAnimation = layoutInsetsDuringAnimation; this.cancellationSignal = cancellationSignal; + this.useInsetsAnimationThread = useInsetsAnimationThread; } final @InsetsType int types; @@ -269,6 +287,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final @AnimationType int animationType; final @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation; final CancellationSignal cancellationSignal; + final boolean useInsetsAnimationThread; } private final String TAG = "InsetsControllerImpl"; @@ -347,15 +366,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation InsetsState state = new InsetsState(mState, true /* copySources */); for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { RunningAnimation runningAnimation = mRunningAnimations.get(i); - InsetsAnimationControlImpl control = runningAnimation.control; + InsetsAnimationControlRunner runner = runningAnimation.runner; + if (runner instanceof InsetsAnimationControlImpl) { + InsetsAnimationControlImpl control = (InsetsAnimationControlImpl) runner; - // Keep track of running animation to be dispatched. Aggregate it here such that if - // it gets finished within applyChangeInsets we still dispatch it to onProgress. - if (runningAnimation.startDispatched) { - mTmpRunningAnims.add(control.getAnimation()); - } - if (control.applyChangeInsets(state)) { - mTmpFinishedControls.add(control); + // Keep track of running animation to be dispatched. Aggregate it here such that + // if it gets finished within applyChangeInsets we still dispatch it to + // onProgress. + if (runningAnimation.startDispatched) { + mTmpRunningAnims.add(control.getAnimation()); + } + + if (control.applyChangeInsets(state)) { + mTmpFinishedControls.add(control); + } } } @@ -499,7 +523,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation pendingRequest.listener, mFrame, true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator, false /* fade */, pendingRequest.animationType, - pendingRequest.layoutInsetsDuringAnimation); + pendingRequest.layoutInsetsDuringAnimation, + pendingRequest.useInsetsAnimationThread); pendingRequest.cancellationSignal.setOnCancelListener(cancellationSignal::cancel); return; } @@ -563,7 +588,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return cancellationSignal; } return controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, interpolator, - false /* fade */, animationType, getLayoutInsetsDuringAnimationMode(types)); + false /* fade */, animationType, getLayoutInsetsDuringAnimationMode(types), + false /* useInsetsAnimationThread */); } private boolean checkDisplayFramesForControlling() { @@ -577,7 +603,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme, long durationMs, Interpolator interpolator, boolean fade, @AnimationType int animationType, - @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { + @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation, + boolean useInsetsAnimationThread) { CancellationSignal cancellationSignal = new CancellationSignal(); if (types == 0) { // nothing to animate. @@ -600,7 +627,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation abortPendingImeControlRequest(); final PendingControlRequest request = new PendingControlRequest(types, listener, durationMs, - interpolator, animationType, layoutInsetsDuringAnimation, cancellationSignal); + interpolator, animationType, layoutInsetsDuringAnimation, cancellationSignal, + useInsetsAnimationThread); mPendingImeControlRequest = request; mHandler.postDelayed(mPendingControlTimeout, PENDING_CONTROL_TIMEOUT_MS); cancellationSignal.setOnCancelListener(() -> { @@ -617,11 +645,21 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return cancellationSignal; } - final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls, - frame, mState, listener, typesReady, this, durationMs, interpolator, fade, - layoutInsetsDuringAnimation, animationType); - mRunningAnimations.add(new RunningAnimation(controller, animationType)); - cancellationSignal.setOnCancelListener(controller::onCancelled); + + final InsetsAnimationControlRunner runner = useInsetsAnimationThread + ? new InsetsAnimationThreadControlRunner(controls, + frame, mState, listener, typesReady, this, durationMs, interpolator, fade, + animationType, mViewRoot.mHandler) + : new InsetsAnimationControlImpl(controls, + frame, mState, listener, typesReady, this, durationMs, interpolator, fade, + animationType); + mRunningAnimations.add(new RunningAnimation(runner, animationType)); + cancellationSignal.setOnCancelListener(runner::cancel); + if (layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) { + showDirectly(types); + } else { + hideDirectly(types, false /* animationFinished */, animationType); + } return cancellationSignal; } @@ -705,7 +743,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private void cancelExistingControllers(@InsetsType int types) { for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { - InsetsAnimationControlImpl control = mRunningAnimations.get(i).control; + InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner; if ((control.getTypes() & types) != 0) { cancelAnimation(control, true /* invokeCallback */); } @@ -725,13 +763,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting @Override - public void notifyFinished(InsetsAnimationControlImpl controller, boolean shown) { - cancelAnimation(controller, false /* invokeCallback */); + public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) { + cancelAnimation(runner, false /* invokeCallback */); if (shown) { - showDirectly(controller.getTypes()); + showDirectly(runner.getTypes()); } else { - hideDirectly(controller.getTypes(), true /* animationFinished */, - controller.getAnimationType()); + hideDirectly(runner.getTypes(), true /* animationFinished */, + runner.getAnimationType()); } } @@ -756,7 +794,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation void notifyControlRevoked(InsetsSourceConsumer consumer) { for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { - InsetsAnimationControlImpl control = mRunningAnimations.get(i).control; + InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner; if ((control.getTypes() & toPublicType(consumer.getType())) != 0) { cancelAnimation(control, true /* invokeCallback */); } @@ -766,12 +804,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } - private void cancelAnimation(InsetsAnimationControlImpl control, boolean invokeCallback) { + private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) { if (invokeCallback) { - control.onCancelled(); + control.cancel(); } for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { - if (mRunningAnimations.get(i).control == control) { + if (mRunningAnimations.get(i).runner == control) { mRunningAnimations.remove(i); break; } @@ -844,7 +882,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public @AnimationType int getAnimationType(@InternalInsetsType int type) { for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { - InsetsAnimationControlImpl control = mRunningAnimations.get(i).control; + InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner; if (control.controlsInternalType(type)) { return mRunningAnimations.get(i).type; } @@ -878,15 +916,24 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return; } + boolean useInsetsAnimationThread = canUseInsetsAnimationThread(); final InternalAnimationControlListener listener = - new InternalAnimationControlListener(show); + new InternalAnimationControlListener(show, useInsetsAnimationThread); // Show/hide animations always need to be relative to the display frame, in order that shown // and hidden state insets are correct. controlAnimationUnchecked( types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(), INTERPOLATOR, true /* fade */, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN - : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN); + : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN, + useInsetsAnimationThread); + } + + private boolean canUseInsetsAnimationThread() { + if (mViewRoot.mView == null) { + return true; + } + return !mViewRoot.mView.hasWindowInsetsAnimationCallback(); } private void hideDirectly( @@ -921,13 +968,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void startAnimation(InsetsAnimationControlImpl controller, WindowInsetsAnimationControlListener listener, int types, - WindowInsetsAnimation animation, Bounds bounds, int layoutDuringAnimation) { - if (layoutDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) { - showDirectly(types); - } else { - hideDirectly(types, false /* animationFinished */, controller.getAnimationType()); - } - + WindowInsetsAnimation animation, Bounds bounds) { if (mViewRoot.mView == null) { return; } @@ -941,7 +982,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { RunningAnimation runningAnimation = mRunningAnimations.get(i); - if (runningAnimation.control == controller) { + if (runningAnimation.runner == controller) { runningAnimation.startDispatched = true; } } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 252fc0c82cf77..332573449e18a 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -117,7 +117,7 @@ public class InsetsSourceConsumer { } } if (lastControl != null) { - lastControl.release(mController); + lastControl.release(mController::releaseSurfaceControlFromRt); } } diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index 75f6eab798ce4..f3ec65f997baf 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -22,6 +22,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.view.InsetsState.InternalInsetsType; +import java.util.function.Consumer; + /** * Represents a parcelable object to allow controlling a single {@link InsetsSource}. * @hide @@ -94,9 +96,9 @@ public class InsetsSourceControl implements Parcelable { dest.writeParcelable(mSurfacePosition, 0 /* flags*/); } - public void release(InsetsController controller) { + public void release(Consumer surfaceReleaseConsumer) { if (mLeash != null) { - controller.releaseSurfaceControlFromRt(mLeash); + surfaceReleaseConsumer.accept(mLeash); } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 6236e6ea31e90..879f2840a1b3b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -11226,6 +11226,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, getListenerInfo().mWindowInsetsAnimationCallback = callback; } + /** + * @return {@code true} if any {@link WindowInsetsAnimation.Callback} is registered on the view + * or view tree of the sub-hierarchy {@code false} otherwise. + * @hide + */ + public boolean hasWindowInsetsAnimationCallback() { + return getListenerInfo().mWindowInsetsAnimationCallback != null; + } + /** * Dispatches {@link WindowInsetsAnimation.Callback#onPrepare(WindowInsetsAnimation)} * when Window Insets animation is being prepared. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 7d4ec3dcf2e78..e34e84c977ea9 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -7258,6 +7258,34 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager : DISPATCH_MODE_CONTINUE_ON_SUBTREE; } + /** + * @hide + */ + @Override + public boolean hasWindowInsetsAnimationCallback() { + if (super.hasWindowInsetsAnimationCallback()) { + return true; + } + + // If we are root-level content view that fits insets, we imitate consuming behavior, so + // no child will retrieve window insets animation callback. + // See dispatchWindowInsetsAnimationPrepare. + boolean isOptionalFitSystemWindows = (mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) != 0 + || isFrameworkOptionalFitsSystemWindows(); + if (isOptionalFitSystemWindows && mAttachInfo != null + && mAttachInfo.mContentOnApplyWindowInsetsListener != null) { + return false; + } + + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + if (getChildAt(i).hasWindowInsetsAnimationCallback()) { + return true; + } + } + return false; + } + @Override public void dispatchWindowInsetsAnimationPrepare( @NonNull WindowInsetsAnimation animation) { diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 01a1c77d50af8..410d9afe73daa 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -504,6 +504,7 @@ public final class WindowManagerGlobal { } void doRemoveView(ViewRootImpl root) { + boolean allViewsRemoved; synchronized (mLock) { final int index = mRoots.indexOf(root); if (index >= 0) { @@ -512,10 +513,17 @@ public final class WindowManagerGlobal { final View view = mViews.remove(index); mDyingViews.remove(view); } + allViewsRemoved = mRoots.isEmpty(); } if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) { doTrimForeground(); } + + // If we don't have any views anymore in our process, we no longer need the + // InsetsAnimationThread to save some resources. + if (allViewsRemoved) { + InsetsAnimationThread.release(); + } } private int findViewLocked(View view, boolean required) { diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index 50cd5a3b01b55..fe25e79df44e1 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -123,7 +123,7 @@ public class InsetsAnimationControlImplTest { mController = new InsetsAnimationControlImpl(controls, new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(), mMockController, 10 /* durationMs */, new LinearInterpolator(), - false /* fade */, LAYOUT_INSETS_DURING_ANIMATION_SHOWN, 0 /* animationType */); + false /* fade */, 0 /* animationType */); } @Test @@ -182,7 +182,7 @@ public class InsetsAnimationControlImplTest { @Test public void testCancelled() { - mController.onCancelled(); + mController.cancel(); try { mController.setInsetsAndAlpha(Insets.NONE, 1f /*alpha */, 0f /* fraction */); fail("Expected exception to be thrown"); diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 023fc1736acaf..f1ab8ddebce62 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -31,6 +31,7 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -47,6 +48,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.CancellationSignal; import android.platform.test.annotations.Presubmit; +import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type; import android.view.WindowInsetsController.OnControllableInsetsChangedListener; @@ -169,11 +171,8 @@ public class InsetsControllerTest { @Test public void testControlsChanged() { - InsetsSourceControl control = - new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()); - mController.onControlsChanged(new InsetsSourceControl[] { control }); - assertEquals(mLeash, - mController.getSourceConsumer(ITYPE_STATUS_BAR).getControl().getLeash()); + mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR)); + assertNotNull(mController.getSourceConsumer(ITYPE_STATUS_BAR).getControl().getLeash()); mController.addOnControllableInsetsChangedListener( ((controller, typeMask) -> assertEquals(statusBars(), typeMask))); } @@ -183,9 +182,7 @@ public class InsetsControllerTest { OnControllableInsetsChangedListener listener = mock(OnControllableInsetsChangedListener.class); mController.addOnControllableInsetsChangedListener(listener); - InsetsSourceControl control = - new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()); - mController.onControlsChanged(new InsetsSourceControl[] { control }); + mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR)); mController.onControlsChanged(new InsetsSourceControl[0]); assertNull(mController.getSourceConsumer(ITYPE_STATUS_BAR).getControl()); InOrder inOrder = Mockito.inOrder(listener); @@ -197,9 +194,7 @@ public class InsetsControllerTest { @Test public void testControlsRevoked_duringAnim() { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - InsetsSourceControl control = - new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()); - mController.onControlsChanged(new InsetsSourceControl[] { control }); + mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR)); WindowInsetsAnimationControlListener mockListener = mock(WindowInsetsAnimationControlListener.class); @@ -262,11 +257,8 @@ public class InsetsControllerTest { @Test public void testApplyImeVisibility() { - final InsetsSourceControl ime = new InsetsSourceControl(ITYPE_IME, mLeash, new Point()); - - InsetsSourceControl[] controls = new InsetsSourceControl[3]; - controls[0] = ime; - mController.onControlsChanged(controls); + InsetsSourceControl ime = createControl(ITYPE_IME); + mController.onControlsChanged(new InsetsSourceControl[] { ime }); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(); mController.applyImeVisibility(true); @@ -429,9 +421,7 @@ public class InsetsControllerTest { @Test public void testRestoreStartsAnimation() { - InsetsSourceControl control = - new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()); - mController.onControlsChanged(new InsetsSourceControl[]{control}); + mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR)); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.hide(Type.statusBars()); @@ -448,7 +438,7 @@ public class InsetsControllerTest { assertTrue(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible()); // Gaining control - mController.onControlsChanged(new InsetsSourceControl[]{control}); + mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR)); assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR)); mController.cancelExistingAnimation(); assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible()); @@ -459,8 +449,6 @@ public class InsetsControllerTest { @Test public void testStartImeAnimationAfterGettingControl() { - InsetsSourceControl control = - new InsetsSourceControl(ITYPE_IME, mLeash, new Point()); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { @@ -471,7 +459,7 @@ public class InsetsControllerTest { mController.show(ime(), true /* fromIme */); // Gaining control shortly after - mController.onControlsChanged(new InsetsSourceControl[]{control}); + mController.onControlsChanged(createSingletonControl(ITYPE_IME)); assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_IME)); mController.cancelExistingAnimation(); @@ -483,16 +471,13 @@ public class InsetsControllerTest { @Test public void testStartImeAnimationAfterGettingControl_imeLater() { - InsetsSourceControl control = - new InsetsSourceControl(ITYPE_IME, mLeash, new Point()); - InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.show(ime()); assertFalse(mController.getState().getSource(ITYPE_IME).isVisible()); // Gaining control shortly after - mController.onControlsChanged(new InsetsSourceControl[]{control}); + mController.onControlsChanged(createSingletonControl(ITYPE_IME)); // Pretend IME is calling mController.show(ime(), true /* fromIme */); @@ -507,9 +492,7 @@ public class InsetsControllerTest { @Test public void testAnimationEndState_controller() throws Exception { - InsetsSourceControl control = - new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()); - mController.onControlsChanged(new InsetsSourceControl[] { control }); + mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR)); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { WindowInsetsAnimationControlListener mockListener = @@ -535,9 +518,7 @@ public class InsetsControllerTest { @Test public void testCancellation_afterGainingControl() throws Exception { - InsetsSourceControl control = - new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()); - mController.onControlsChanged(new InsetsSourceControl[] { control }); + mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR)); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { WindowInsetsAnimationControlListener mockListener = @@ -655,12 +636,23 @@ public class InsetsControllerTest { latch.await(); } + private InsetsSourceControl createControl(@InternalInsetsType int type) { + + // Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will + // attempt to release mLeash directly. + SurfaceControl copy = new SurfaceControl(); + copy.copyFrom(mLeash); + return new InsetsSourceControl(type, copy, new Point()); + } + + private InsetsSourceControl[] createSingletonControl(@InternalInsetsType int type) { + return new InsetsSourceControl[] { createControl(type) }; + } + private InsetsSourceControl[] prepareControls() { - final InsetsSourceControl navBar = new InsetsSourceControl(ITYPE_NAVIGATION_BAR, mLeash, - new Point()); - final InsetsSourceControl statusBar = new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, - new Point()); - final InsetsSourceControl ime = new InsetsSourceControl(ITYPE_IME, mLeash, new Point()); + final InsetsSourceControl navBar = createControl(ITYPE_NAVIGATION_BAR); + final InsetsSourceControl statusBar = createControl(ITYPE_STATUS_BAR); + final InsetsSourceControl ime = createControl(ITYPE_IME); InsetsSourceControl[] controls = new InsetsSourceControl[3]; controls[0] = navBar; diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 01f98888c7778..958c8af3af249 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -36,6 +36,7 @@ import android.util.IntArray; import android.util.SparseArray; import android.view.InsetsAnimationControlCallbacks; import android.view.InsetsAnimationControlImpl; +import android.view.InsetsAnimationControlRunner; import android.view.InsetsController; import android.view.InsetsSourceControl; import android.view.InsetsState; @@ -44,6 +45,7 @@ import android.view.SurfaceControl; import android.view.SyncRtSurfaceTransactionApplier; import android.view.ViewRootImpl; import android.view.WindowInsetsAnimation; +import android.view.WindowInsetsAnimation.Bounds; import android.view.WindowInsetsAnimationControlListener; import com.android.internal.annotations.VisibleForTesting; @@ -327,7 +329,7 @@ class InsetsPolicy { InsetsPolicyAnimationControlCallbacks mControlCallbacks; InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback) { - super(show); + super(show, true /* useSfVsync */); mFinishCallback = finishCallback; mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this); } @@ -360,8 +362,6 @@ class InsetsPolicy { mFocusedWin.getDisplayContent().getBounds(), getState(), mListener, typesReady, this, mListener.getDurationMs(), InsetsController.INTERPOLATOR, true, - show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN - : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE); SurfaceAnimationThread.getHandler().post( () -> mListener.onReady(mAnimationControl, typesReady)); @@ -377,7 +377,7 @@ class InsetsPolicy { } @Override - public void notifyFinished(InsetsAnimationControlImpl controller, boolean shown) { + public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) { // Nothing's needed here. Finish steps is handled in the listener // onAnimationFinished callback. } @@ -406,14 +406,14 @@ class InsetsPolicy { applyParams(t, surfaceParams, mTmpFloat9); } t.apply(); + t.close(); } @Override public void startAnimation(InsetsAnimationControlImpl controller, WindowInsetsAnimationControlListener listener, int types, WindowInsetsAnimation animation, - WindowInsetsAnimation.Bounds bounds, - int layoutDuringAnimation) { + Bounds bounds) { } } }