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) { } } }