diff options
| author | 2023-10-16 12:15:41 +0000 | |
|---|---|---|
| committer | 2023-10-16 12:15:41 +0000 | |
| commit | c810a96d2b9488cba5784afe5b9627c33aa82757 (patch) | |
| tree | 5202e2f2a3e6a67dde84b3d8bb300c046698fab1 | |
| parent | fffc7a5384615d7e1e20cba2b260b8747890347d (diff) | |
| parent | 6321bda515c7e2d42d8358b4d977208229190596 (diff) | |
Merge "Apply dim changes only at the end of traversal" into main
13 files changed, 1075 insertions, 379 deletions
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 7c931cd9fa15..9caf87f32b8e 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -13,3 +13,10 @@ flag { description: "On close to square display, when necessary, configuration includes status bar" bug: "291870756" } + +flag { + name: "dimmer_refactor" + namespace: "windowing_frontend" + description: "Refactor dim to fix flickers" + bug: "281632483,295291019" +}
\ No newline at end of file diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index ec525f09fa88..4bb7c33b41e2 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -91,6 +91,8 @@ public enum ProtoLogGroup implements IProtoLogGroup { WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, "CoreBackPreview"), WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM), + + WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), TEST_GROUP(true, true, false, "WindowManagerProtoLogTest"); private final boolean mEnabled; diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 9c6528785584..b71aaf3fc2e6 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1801,6 +1801,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-504637678": { + "message": "Starting animation on dim layer %s, requested by %s, alpha: %f -> %f, blur: %d -> %d", + "level": "VERBOSE", + "group": "WM_DEBUG_DIMMER", + "at": "com\/android\/server\/wm\/SmoothDimmer.java" + }, "-503656156": { "message": "Update process config of %s to new config %s", "level": "VERBOSE", @@ -3739,6 +3745,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityClientController.java" }, + "1309365288": { + "message": "Removing dim surface %s on transaction %s", + "level": "DEBUG", + "group": "WM_DEBUG_DIMMER", + "at": "com\/android\/server\/wm\/SmoothDimmer.java" + }, "1316533291": { "message": "State movement: %s from:%s to:%s reason:%s", "level": "VERBOSE", @@ -4003,6 +4015,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1620751818": { + "message": "Dim %s skipping animation and directly setting alpha=%f, blur=%d", + "level": "DEBUG", + "group": "WM_DEBUG_DIMMER", + "at": "com\/android\/server\/wm\/SmoothDimmer.java" + }, "1621562070": { "message": " startWCT=%s", "level": "VERBOSE", @@ -4560,6 +4578,9 @@ "WM_DEBUG_CONTENT_RECORDING": { "tag": "WindowManager" }, + "WM_DEBUG_DIMMER": { + "tag": "WindowManager" + }, "WM_DEBUG_DRAW": { "tag": "WindowManager" }, diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java index b039646c1697..3dc377dbc14c 100644 --- a/services/core/java/com/android/server/wm/AnimationAdapter.java +++ b/services/core/java/com/android/server/wm/AnimationAdapter.java @@ -22,6 +22,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.animation.Animation; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; @@ -31,7 +32,8 @@ import java.io.PrintWriter; * Interface that describes an animation and bridges the animation start to the component * responsible for running the animation. */ -interface AnimationAdapter { +@VisibleForTesting +public interface AnimationAdapter { long STATUS_BAR_TRANSITION_DURATION = 120L; diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index ae29afa9fc49..64a230effb38 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2023 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. @@ -11,172 +11,36 @@ * 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 + * limitations under the License. */ package com.android.server.wm; -import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS; -import static com.android.server.wm.AlphaAnimationSpecProto.FROM; -import static com.android.server.wm.AlphaAnimationSpecProto.TO; -import static com.android.server.wm.AnimationSpecProto.ALPHA; -import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; - import android.annotation.NonNull; import android.graphics.Rect; -import android.util.Log; -import android.util.proto.ProtoOutputStream; -import android.view.Surface; import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.wm.SurfaceAnimator.AnimationType; - -import java.io.PrintWriter; +import com.android.window.flags.Flags; /** * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is * black layers of varying opacity at various Z-levels which create the effect of a Dim. */ -class Dimmer { - private static final String TAG = "WindowManager"; - // This is in milliseconds. - private static final int DEFAULT_DIM_ANIM_DURATION = 200; - - private class DimAnimatable implements SurfaceAnimator.Animatable { - private SurfaceControl mDimLayer; - - private DimAnimatable(SurfaceControl dimLayer) { - mDimLayer = dimLayer; - } - - @Override - public SurfaceControl.Transaction getSyncTransaction() { - return mHost.getSyncTransaction(); - } - - @Override - public SurfaceControl.Transaction getPendingTransaction() { - return mHost.getPendingTransaction(); - } - - @Override - public void commitPendingTransaction() { - mHost.commitPendingTransaction(); - } - - @Override - public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) { - } - - @Override - public void onAnimationLeashLost(SurfaceControl.Transaction t) { - } - - @Override - public SurfaceControl.Builder makeAnimationLeash() { - return mHost.makeAnimationLeash(); - } - - @Override - public SurfaceControl getAnimationLeashParent() { - return mHost.getSurfaceControl(); - } - - @Override - public SurfaceControl getSurfaceControl() { - return mDimLayer; - } - - @Override - public SurfaceControl getParentSurfaceControl() { - return mHost.getSurfaceControl(); - } - - @Override - public int getSurfaceWidth() { - // This will determine the size of the leash created. This should be the size of the - // host and not the dim layer since the dim layer may get bigger during animation. If - // that occurs, the leash size cannot change so we need to ensure the leash is big - // enough that the dim layer can grow. - // This works because the mHost will be a Task which has the display bounds. - return mHost.getSurfaceWidth(); - } - - @Override - public int getSurfaceHeight() { - // See getSurfaceWidth() above for explanation. - return mHost.getSurfaceHeight(); - } - - void removeSurface() { - if (mDimLayer != null && mDimLayer.isValid()) { - getSyncTransaction().remove(mDimLayer); - } - mDimLayer = null; - } - } - - @VisibleForTesting - class DimState { - /** - * The layer where property changes should be invoked on. - */ - SurfaceControl mDimLayer; - boolean mDimming; - boolean isVisible; - SurfaceAnimator mSurfaceAnimator; - - // TODO(b/64816140): Remove after confirming dimmer layer always matches its container. - final Rect mDimBounds = new Rect(); - - /** - * Determines whether the dim layer should animate before destroying. - */ - boolean mAnimateExit = true; - - /** - * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for - * details on Dim lifecycle. - */ - boolean mDontReset; - - DimState(SurfaceControl dimLayer) { - mDimLayer = dimLayer; - mDimming = true; - final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer); - mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> { - if (!mDimming) { - dimAnimatable.removeSurface(); - } - }, mHost.mWmService); - } - } - +public abstract class Dimmer { /** - * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the + * The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the * host, some controller of it, or one of the hosts children. */ - private WindowContainer mHost; - private WindowContainer mLastRequestedDimContainer; - @VisibleForTesting - DimState mDimState; - - private final SurfaceAnimatorStarter mSurfaceAnimatorStarter; - - @VisibleForTesting - interface SurfaceAnimatorStarter { - void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, - AnimationAdapter anim, boolean hidden, @AnimationType int type); - } + protected final WindowContainer mHost; - Dimmer(WindowContainer host) { - this(host, SurfaceAnimator::startAnimation); + protected Dimmer(WindowContainer host) { + mHost = host; } - Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) { - mHost = host; - mSurfaceAnimatorStarter = surfaceAnimatorStarter; + // Constructs the correct type of dimmer + static Dimmer create(WindowContainer host) { + return Flags.dimmerRefactor() ? new SmoothDimmer(host) : new LegacyDimmer(host); } @NonNull @@ -184,49 +48,8 @@ class Dimmer { return mHost; } - private SurfaceControl makeDimLayer() { - return mHost.makeChildSurface(null) - .setParent(mHost.getSurfaceControl()) - .setColorLayer() - .setName("Dim Layer for - " + mHost.getName()) - .setCallsite("Dimmer.makeDimLayer") - .build(); - } - - /** - * Retrieve the DimState, creating one if it doesn't exist. - */ - private DimState getDimState(WindowContainer container) { - if (mDimState == null) { - try { - final SurfaceControl ctl = makeDimLayer(); - mDimState = new DimState(ctl); - } catch (Surface.OutOfResourcesException e) { - Log.w(TAG, "OutOfResourcesException creating dim surface"); - } - } - - mLastRequestedDimContainer = container; - return mDimState; - } - - private void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) { - final DimState d = getDimState(container); - - if (d == null) { - return; - } - - // The dim method is called from WindowState.prepareSurfaces(), which is always called - // in the correct Z from lowest Z to highest. This ensures that the dim layer is always - // relative to the highest Z layer with a dim. - SurfaceControl.Transaction t = mHost.getPendingTransaction(); - t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); - t.setAlpha(d.mDimLayer, alpha); - t.setBackgroundBlurRadius(d.mDimLayer, blurRadius); - - d.mDimming = true; - } + protected abstract void dim( + WindowContainer container, int relativeLayer, float alpha, int blurRadius); /** * Place a dim above the given container, which should be a child of the host container. @@ -260,25 +83,15 @@ class Dimmer { * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them * a chance to request dims to continue. */ - void resetDimStates() { - if (mDimState == null) { - return; - } - if (!mDimState.mDontReset) { - mDimState.mDimming = false; - } - } + abstract void resetDimStates(); /** Returns non-null bounds if the dimmer is showing. */ - Rect getDimBounds() { - return mDimState != null ? mDimState.mDimBounds : null; - } + abstract Rect getDimBounds(); - void dontAnimateExit() { - if (mDimState != null) { - mDimState.mAnimateExit = false; - } - } + abstract void dontAnimateExit(); + + @VisibleForTesting + abstract SurfaceControl getDimLayer(); /** * Call after invoking {@link WindowContainer#prepareSurfaces} on children as @@ -288,109 +101,5 @@ class Dimmer { * @param t A transaction in which to update the dims. * @return true if any Dims were updated. */ - boolean updateDims(SurfaceControl.Transaction t) { - if (mDimState == null) { - return false; - } - - if (!mDimState.mDimming) { - if (!mDimState.mAnimateExit) { - if (mDimState.mDimLayer.isValid()) { - t.remove(mDimState.mDimLayer); - } - } else { - startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t); - } - mDimState = null; - return false; - } else { - final Rect bounds = mDimState.mDimBounds; - // TODO: Once we use geometry from hierarchy this falls away. - t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top); - t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height()); - if (!mDimState.isVisible) { - mDimState.isVisible = true; - t.show(mDimState.mDimLayer); - // Skip enter animation while starting window is on top of its activity - final WindowState ws = mLastRequestedDimContainer.asWindowState(); - if (ws == null || ws.mActivityRecord == null - || ws.mActivityRecord.mStartingData == null) { - startDimEnter(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t); - } - } - return true; - } - } - - private void startDimEnter(WindowContainer container, SurfaceAnimator animator, - SurfaceControl.Transaction t) { - startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */); - } - - private void startDimExit(WindowContainer container, SurfaceAnimator animator, - SurfaceControl.Transaction t) { - startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */); - } - - private void startAnim(WindowContainer container, SurfaceAnimator animator, - SurfaceControl.Transaction t, float startAlpha, float endAlpha) { - mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter( - new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)), - mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */, - ANIMATION_TYPE_DIMMER); - } - - private long getDimDuration(WindowContainer container) { - // If there's no container, then there isn't an animation occurring while dimming. Set the - // duration to 0 so it immediately dims to the set alpha. - if (container == null) { - return 0; - } - - // Otherwise use the same duration as the animation on the WindowContainer - AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); - final float durationScale = container.mWmService.getTransitionAnimationScaleLocked(); - return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale) - : animationAdapter.getDurationHint(); - } - - private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec { - private final long mDuration; - private final float mFromAlpha; - private final float mToAlpha; - - AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) { - mFromAlpha = fromAlpha; - mToAlpha = toAlpha; - mDuration = duration; - } - - @Override - public long getDuration() { - return mDuration; - } - - @Override - public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) { - final float fraction = getFraction(currentPlayTime); - final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha; - t.setAlpha(sc, alpha); - } - - @Override - public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("from="); pw.print(mFromAlpha); - pw.print(" to="); pw.print(mToAlpha); - pw.print(" duration="); pw.println(mDuration); - } - - @Override - public void dumpDebugInner(ProtoOutputStream proto) { - final long token = proto.start(ALPHA); - proto.write(FROM, mFromAlpha); - proto.write(TO, mToAlpha); - proto.write(DURATION_MS, mDuration); - proto.end(token); - } - } + abstract boolean updateDims(SurfaceControl.Transaction t); } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index df26b101a657..f51bf7fbf27a 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -54,7 +54,6 @@ import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; - /** * Container for grouping WindowContainer below DisplayContent. * @@ -786,7 +785,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { * DisplayArea that can be dimmed. */ static class Dimmable extends DisplayArea<DisplayArea> { - private final Dimmer mDimmer = new Dimmer(this); + private final Dimmer mDimmer = Dimmer.create(this); Dimmable(WindowManagerService wms, Type type, String name, int featureId) { super(wms, type, name, featureId); diff --git a/services/core/java/com/android/server/wm/LegacyDimmer.java b/services/core/java/com/android/server/wm/LegacyDimmer.java new file mode 100644 index 000000000000..ccf956ecef1e --- /dev/null +++ b/services/core/java/com/android/server/wm/LegacyDimmer.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2023 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 com.android.server.wm; + +import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS; +import static com.android.server.wm.AlphaAnimationSpecProto.FROM; +import static com.android.server.wm.AlphaAnimationSpecProto.TO; +import static com.android.server.wm.AnimationSpecProto.ALPHA; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.graphics.Rect; +import android.util.Log; +import android.util.proto.ProtoOutputStream; +import android.view.Surface; +import android.view.SurfaceControl; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.PrintWriter; + +public class LegacyDimmer extends Dimmer { + private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM; + // This is in milliseconds. + private static final int DEFAULT_DIM_ANIM_DURATION = 200; + DimState mDimState; + private WindowContainer mLastRequestedDimContainer; + private final SurfaceAnimatorStarter mSurfaceAnimatorStarter; + + private class DimAnimatable implements SurfaceAnimator.Animatable { + private SurfaceControl mDimLayer; + + private DimAnimatable(SurfaceControl dimLayer) { + mDimLayer = dimLayer; + } + + @Override + public SurfaceControl.Transaction getSyncTransaction() { + return mHost.getSyncTransaction(); + } + + @Override + public SurfaceControl.Transaction getPendingTransaction() { + return mHost.getPendingTransaction(); + } + + @Override + public void commitPendingTransaction() { + mHost.commitPendingTransaction(); + } + + @Override + public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) { + } + + @Override + public void onAnimationLeashLost(SurfaceControl.Transaction t) { + } + + @Override + public SurfaceControl.Builder makeAnimationLeash() { + return mHost.makeAnimationLeash(); + } + + @Override + public SurfaceControl getAnimationLeashParent() { + return mHost.getSurfaceControl(); + } + + @Override + public SurfaceControl getSurfaceControl() { + return mDimLayer; + } + + @Override + public SurfaceControl getParentSurfaceControl() { + return mHost.getSurfaceControl(); + } + + @Override + public int getSurfaceWidth() { + // This will determine the size of the leash created. This should be the size of the + // host and not the dim layer since the dim layer may get bigger during animation. If + // that occurs, the leash size cannot change so we need to ensure the leash is big + // enough that the dim layer can grow. + // This works because the mHost will be a Task which has the display bounds. + return mHost.getSurfaceWidth(); + } + + @Override + public int getSurfaceHeight() { + // See getSurfaceWidth() above for explanation. + return mHost.getSurfaceHeight(); + } + + void removeSurface() { + if (mDimLayer != null && mDimLayer.isValid()) { + getSyncTransaction().remove(mDimLayer); + } + mDimLayer = null; + } + } + + @VisibleForTesting + class DimState { + /** + * The layer where property changes should be invoked on. + */ + SurfaceControl mDimLayer; + boolean mDimming; + boolean mIsVisible; + + // TODO(b/64816140): Remove after confirming dimmer layer always matches its container. + final Rect mDimBounds = new Rect(); + + /** + * Determines whether the dim layer should animate before destroying. + */ + boolean mAnimateExit = true; + + /** + * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for + * details on Dim lifecycle. + */ + boolean mDontReset; + SurfaceAnimator mSurfaceAnimator; + + DimState(SurfaceControl dimLayer) { + mDimLayer = dimLayer; + mDimming = true; + final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer); + mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> { + if (!mDimming) { + dimAnimatable.removeSurface(); + } + }, mHost.mWmService); + } + } + + @VisibleForTesting + interface SurfaceAnimatorStarter { + void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, + AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type); + } + + protected LegacyDimmer(WindowContainer host) { + this(host, SurfaceAnimator::startAnimation); + } + + LegacyDimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) { + super(host); + mSurfaceAnimatorStarter = surfaceAnimatorStarter; + } + + private DimState obtainDimState(WindowContainer container) { + if (mDimState == null) { + try { + final SurfaceControl ctl = makeDimLayer(); + mDimState = new DimState(ctl); + } catch (Surface.OutOfResourcesException e) { + Log.w(TAG, "OutOfResourcesException creating dim surface"); + } + } + + mLastRequestedDimContainer = container; + return mDimState; + } + + private SurfaceControl makeDimLayer() { + return mHost.makeChildSurface(null) + .setParent(mHost.getSurfaceControl()) + .setColorLayer() + .setName("Dim Layer for - " + mHost.getName()) + .setCallsite("Dimmer.makeDimLayer") + .build(); + } + + @Override + SurfaceControl getDimLayer() { + return mDimState != null ? mDimState.mDimLayer : null; + } + + @Override + void resetDimStates() { + if (mDimState == null) { + return; + } + if (!mDimState.mDontReset) { + mDimState.mDimming = false; + } + } + + @Override + Rect getDimBounds() { + return mDimState != null ? mDimState.mDimBounds : null; + } + + @Override + void dontAnimateExit() { + if (mDimState != null) { + mDimState.mAnimateExit = false; + } + } + + @Override + protected void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) { + final DimState d = obtainDimState(container); + + if (d == null) { + return; + } + + // The dim method is called from WindowState.prepareSurfaces(), which is always called + // in the correct Z from lowest Z to highest. This ensures that the dim layer is always + // relative to the highest Z layer with a dim. + SurfaceControl.Transaction t = mHost.getPendingTransaction(); + t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); + t.setAlpha(d.mDimLayer, alpha); + t.setBackgroundBlurRadius(d.mDimLayer, blurRadius); + + d.mDimming = true; + } + + @Override + boolean updateDims(SurfaceControl.Transaction t) { + if (mDimState == null) { + return false; + } + + if (!mDimState.mDimming) { + if (!mDimState.mAnimateExit) { + if (mDimState.mDimLayer.isValid()) { + t.remove(mDimState.mDimLayer); + } + } else { + startDimExit(mLastRequestedDimContainer, + mDimState.mSurfaceAnimator, t); + } + mDimState = null; + return false; + } else { + final Rect bounds = mDimState.mDimBounds; + // TODO: Once we use geometry from hierarchy this falls away. + t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top); + t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height()); + if (!mDimState.mIsVisible) { + mDimState.mIsVisible = true; + t.show(mDimState.mDimLayer); + // Skip enter animation while starting window is on top of its activity + final WindowState ws = mLastRequestedDimContainer.asWindowState(); + if (ws == null || ws.mActivityRecord == null + || ws.mActivityRecord.mStartingData == null) { + startDimEnter(mLastRequestedDimContainer, + mDimState.mSurfaceAnimator, t); + } + } + return true; + } + } + + private long getDimDuration(WindowContainer container) { + // Use the same duration as the animation on the WindowContainer + AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); + final float durationScale = container.mWmService.getTransitionAnimationScaleLocked(); + return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale) + : animationAdapter.getDurationHint(); + } + + private void startDimEnter(WindowContainer container, SurfaceAnimator animator, + SurfaceControl.Transaction t) { + startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */); + } + + private void startDimExit(WindowContainer container, SurfaceAnimator animator, + SurfaceControl.Transaction t) { + startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */); + } + + private void startAnim(WindowContainer container, SurfaceAnimator animator, + SurfaceControl.Transaction t, float startAlpha, float endAlpha) { + mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter( + new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)), + mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */, + ANIMATION_TYPE_DIMMER); + } + + private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec { + private final long mDuration; + private final float mFromAlpha; + private final float mToAlpha; + + AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) { + mFromAlpha = fromAlpha; + mToAlpha = toAlpha; + mDuration = duration; + } + + @Override + public long getDuration() { + return mDuration; + } + + @Override + public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) { + final float fraction = getFraction(currentPlayTime); + final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha; + t.setAlpha(sc, alpha); + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("from="); pw.print(mFromAlpha); + pw.print(" to="); pw.print(mToAlpha); + pw.print(" duration="); pw.println(mDuration); + } + + @Override + public void dumpDebugInner(ProtoOutputStream proto) { + final long token = proto.start(ALPHA); + proto.write(FROM, mFromAlpha); + proto.write(TO, mToAlpha); + proto.write(DURATION_MS, mDuration); + proto.end(token); + } + } +} diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java new file mode 100644 index 000000000000..6ddbd2c8eb67 --- /dev/null +++ b/services/core/java/com/android/server/wm/SmoothDimmer.java @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2023 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 com.android.server.wm; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER; +import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS; +import static com.android.server.wm.AlphaAnimationSpecProto.FROM; +import static com.android.server.wm.AlphaAnimationSpecProto.TO; +import static com.android.server.wm.AnimationSpecProto.ALPHA; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.graphics.Rect; +import android.util.Log; +import android.util.proto.ProtoOutputStream; +import android.view.Surface; +import android.view.SurfaceControl; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; + +import java.io.PrintWriter; + +class SmoothDimmer extends Dimmer { + private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM; + private static final float EPSILON = 0.0001f; + // This is in milliseconds. + private static final int DEFAULT_DIM_ANIM_DURATION = 200; + DimState mDimState; + private WindowContainer mLastRequestedDimContainer; + private final AnimationAdapterFactory mAnimationAdapterFactory; + + @VisibleForTesting + class DimState { + /** + * The layer where property changes should be invoked on. + */ + SurfaceControl mDimLayer; + boolean mDimming; + boolean mIsVisible; + + // TODO(b/64816140): Remove after confirming dimmer layer always matches its container. + final Rect mDimBounds = new Rect(); + + /** + * Determines whether the dim layer should animate before destroying. + */ + boolean mAnimateExit = true; + + /** + * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for + * details on Dim lifecycle. + */ + boolean mDontReset; + + Change mCurrentProperties; + Change mRequestedProperties; + private AnimationSpec mAlphaAnimationSpec; + private AnimationAdapter mLocalAnimationAdapter; + + static class Change { + private float mAlpha = -1f; + private int mBlurRadius = -1; + private WindowContainer mDimmingContainer = null; + private int mRelativeLayer = -1; + private boolean mSkipAnimation = false; + + Change() {} + + Change(Change other) { + mAlpha = other.mAlpha; + mBlurRadius = other.mBlurRadius; + mDimmingContainer = other.mDimmingContainer; + mRelativeLayer = other.mRelativeLayer; + } + + @Override + public String toString() { + return "Dim state: alpha=" + mAlpha + ", blur=" + mBlurRadius + ", container=" + + mDimmingContainer + ", relativePosition=" + mRelativeLayer + + ", skipAnimation=" + mSkipAnimation; + } + } + + DimState(SurfaceControl dimLayer) { + mDimLayer = dimLayer; + mDimming = true; + mCurrentProperties = new Change(); + mRequestedProperties = new Change(); + } + + void setExitParameters(WindowContainer container) { + setRequestedParameters(container, -1, 0, 0); + } + // Sets a requested change without applying it immediately + void setRequestedParameters(WindowContainer container, int relativeLayer, float alpha, + int blurRadius) { + mRequestedProperties.mDimmingContainer = container; + mRequestedProperties.mRelativeLayer = relativeLayer; + mRequestedProperties.mAlpha = alpha; + mRequestedProperties.mBlurRadius = blurRadius; + } + + /** + * Commit the last changes we received. Called after + * {@link Change#setRequestedParameters(WindowContainer, int, float, int)} + */ + void applyChanges(SurfaceControl.Transaction t) { + if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) { + Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer + + "does not have a surface"); + return; + } + if (!mDimState.mIsVisible) { + mDimState.mIsVisible = true; + t.show(mDimState.mDimLayer); + } + t.setRelativeLayer(mDimLayer, + mRequestedProperties.mDimmingContainer.getSurfaceControl(), + mRequestedProperties.mRelativeLayer); + + if (aspectChanged()) { + if (isAnimating()) { + mLocalAnimationAdapter.onAnimationCancelled(mDimLayer); + } + if (mRequestedProperties.mSkipAnimation + || (!dimmingContainerChanged() && mDimming)) { + // If the dimming container has not changed, then it is running its own + // animation, thus we can directly set the values we get requested, unless it's + // the exiting animation + ProtoLog.d(WM_DEBUG_DIMMER, + "Dim %s skipping animation and directly setting alpha=%f, blur=%d", + mDimLayer, mRequestedProperties.mAlpha, + mRequestedProperties.mBlurRadius); + t.setAlpha(mDimLayer, mRequestedProperties.mAlpha); + t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius); + mRequestedProperties.mSkipAnimation = false; + } else { + startAnimation(t); + } + } + mCurrentProperties = new Change(mRequestedProperties); + } + + private void startAnimation(SurfaceControl.Transaction t) { + mAlphaAnimationSpec = getRequestedAnimationSpec(mRequestedProperties.mAlpha, + mRequestedProperties.mBlurRadius); + mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec, + mHost.mWmService.mSurfaceAnimationRunner); + + mLocalAnimationAdapter.startAnimation(mDimLayer, t, + ANIMATION_TYPE_DIMMER, (type, animator) -> { + t.setAlpha(mDimLayer, mRequestedProperties.mAlpha); + t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius); + if (mRequestedProperties.mAlpha == 0f && !mDimming) { + ProtoLog.d(WM_DEBUG_DIMMER, + "Removing dim surface %s on transaction %s", mDimLayer, t); + t.remove(mDimLayer); + } + mLocalAnimationAdapter = null; + mAlphaAnimationSpec = null; + }); + } + + private boolean isAnimating() { + return mAlphaAnimationSpec != null; + } + + private boolean aspectChanged() { + return Math.abs(mRequestedProperties.mAlpha - mCurrentProperties.mAlpha) > EPSILON + || mRequestedProperties.mBlurRadius != mCurrentProperties.mBlurRadius; + } + + private boolean dimmingContainerChanged() { + return mRequestedProperties.mDimmingContainer != mCurrentProperties.mDimmingContainer; + } + + private AnimationSpec getRequestedAnimationSpec(float targetAlpha, int targetBlur) { + final float startAlpha; + final int startBlur; + if (mAlphaAnimationSpec != null) { + startAlpha = mAlphaAnimationSpec.mCurrentAlpha; + startBlur = mAlphaAnimationSpec.mCurrentBlur; + } else { + startAlpha = Math.max(mCurrentProperties.mAlpha, 0f); + startBlur = Math.max(mCurrentProperties.mBlurRadius, 0); + } + long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer) + * Math.abs(targetAlpha - startAlpha)); + + ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on dim layer %s, requested by %s, " + + "alpha: %f -> %f, blur: %d -> %d", + mDimLayer, mRequestedProperties.mDimmingContainer, startAlpha, targetAlpha, + startBlur, targetBlur); + return new AnimationSpec( + new AnimationExtremes<>(startAlpha, targetAlpha), + new AnimationExtremes<>(startBlur, targetBlur), + duration + ); + } + } + + protected SmoothDimmer(WindowContainer host) { + this(host, new AnimationAdapterFactory()); + } + + @VisibleForTesting + SmoothDimmer(WindowContainer host, AnimationAdapterFactory animationFactory) { + super(host); + mAnimationAdapterFactory = animationFactory; + } + + private DimState obtainDimState(WindowContainer container) { + if (mDimState == null) { + try { + final SurfaceControl ctl = makeDimLayer(); + mDimState = new DimState(ctl); + } catch (Surface.OutOfResourcesException e) { + Log.w(TAG, "OutOfResourcesException creating dim surface"); + } + } + + mLastRequestedDimContainer = container; + return mDimState; + } + + private SurfaceControl makeDimLayer() { + return mHost.makeChildSurface(null) + .setParent(mHost.getSurfaceControl()) + .setColorLayer() + .setName("Dim Layer for - " + mHost.getName()) + .setCallsite("Dimmer.makeDimLayer") + .build(); + } + + @Override + SurfaceControl getDimLayer() { + return mDimState != null ? mDimState.mDimLayer : null; + } + + @Override + void resetDimStates() { + if (mDimState == null) { + return; + } + if (!mDimState.mDontReset) { + mDimState.mDimming = false; + } + } + + @Override + Rect getDimBounds() { + return mDimState != null ? mDimState.mDimBounds : null; + } + + @Override + void dontAnimateExit() { + if (mDimState != null) { + mDimState.mAnimateExit = false; + } + } + + @Override + protected void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) { + final DimState d = obtainDimState(container); + + mDimState.mRequestedProperties.mDimmingContainer = container; + mDimState.setRequestedParameters(container, relativeLayer, alpha, blurRadius); + d.mDimming = true; + } + + boolean updateDims(SurfaceControl.Transaction t) { + if (mDimState == null) { + return false; + } + + if (!mDimState.mDimming) { + // No one is dimming anymore, fade out dim and remove + if (!mDimState.mAnimateExit) { + if (mDimState.mDimLayer.isValid()) { + t.remove(mDimState.mDimLayer); + } + } else { + mDimState.setExitParameters( + mDimState.mRequestedProperties.mDimmingContainer); + mDimState.applyChanges(t); + } + mDimState = null; + return false; + } + final Rect bounds = mDimState.mDimBounds; + // TODO: Once we use geometry from hierarchy this falls away. + t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top); + t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height()); + // Skip enter animation while starting window is on top of its activity + final WindowState ws = mLastRequestedDimContainer.asWindowState(); + if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null + && ws.mActivityRecord.mStartingData != null) { + mDimState.mRequestedProperties.mSkipAnimation = true; + } + mDimState.applyChanges(t); + return true; + } + + private long getDimDuration(WindowContainer container) { + // Use the same duration as the animation on the WindowContainer + AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); + final float durationScale = container.mWmService.getTransitionAnimationScaleLocked(); + return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale) + : animationAdapter.getDurationHint(); + } + + private static class AnimationExtremes<T> { + final T mStartValue; + final T mFinishValue; + + AnimationExtremes(T fromValue, T toValue) { + mStartValue = fromValue; + mFinishValue = toValue; + } + } + + private static class AnimationSpec implements LocalAnimationAdapter.AnimationSpec { + private final long mDuration; + private final AnimationExtremes<Float> mAlpha; + private final AnimationExtremes<Integer> mBlur; + + float mCurrentAlpha = 0; + int mCurrentBlur = 0; + + AnimationSpec(AnimationExtremes<Float> alpha, + AnimationExtremes<Integer> blur, long duration) { + mAlpha = alpha; + mBlur = blur; + mDuration = duration; + } + + @Override + public long getDuration() { + return mDuration; + } + + @Override + public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) { + final float fraction = getFraction(currentPlayTime); + mCurrentAlpha = + fraction * (mAlpha.mFinishValue - mAlpha.mStartValue) + mAlpha.mStartValue; + mCurrentBlur = + (int) fraction * (mBlur.mFinishValue - mBlur.mStartValue) + mBlur.mStartValue; + t.setAlpha(sc, mCurrentAlpha); + t.setBackgroundBlurRadius(sc, mCurrentBlur); + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("from_alpha="); pw.print(mAlpha.mStartValue); + pw.print(" to_alpha="); pw.print(mAlpha.mFinishValue); + pw.print(prefix); pw.print("from_blur="); pw.print(mBlur.mStartValue); + pw.print(" to_blur="); pw.print(mBlur.mFinishValue); + pw.print(" duration="); pw.println(mDuration); + } + + @Override + public void dumpDebugInner(ProtoOutputStream proto) { + final long token = proto.start(ALPHA); + proto.write(FROM, mAlpha.mStartValue); + proto.write(TO, mAlpha.mFinishValue); + proto.write(DURATION_MS, mDuration); + proto.end(token); + } + } + + static class AnimationAdapterFactory { + + public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec, + SurfaceAnimationRunner runner) { + return new LocalAnimationAdapter(alphaAnimationSpec, runner); + } + } +} diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 408ea6eb8c1a..c3de4d5acc21 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -49,7 +49,8 @@ import java.util.function.Supplier; * {@link AnimationAdapter}. When the animation is done animating, our callback to finish the * animation will be invoked, at which we reparent the children back to the original parent. */ -class SurfaceAnimator { +@VisibleForTesting +public class SurfaceAnimator { private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM; @@ -617,7 +618,8 @@ class SurfaceAnimator { * Callback to be passed into {@link AnimationAdapter#startAnimation} to be invoked by the * component that is running the animation when the animation is finished. */ - interface OnAnimationFinishedCallback { + @VisibleForTesting + public interface OnAnimationFinishedCallback { void onAnimationFinished(@AnimationType int type, AnimationAdapter anim); } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 50bc825d8bea..4a3314d012a0 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -103,6 +103,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.server.am.HostingRecord; import com.android.server.pm.pkg.AndroidPackage; +import com.android.window.flags.Flags; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -209,7 +210,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ int mMinHeight; - Dimmer mDimmer = new Dimmer(this); + Dimmer mDimmer = Flags.dimmerRefactor() + ? new SmoothDimmer(this) : new LegacyDimmer(this); /** This task fragment will be removed when the cleanup of its children are done. */ private boolean mIsRemovalRequested; diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java index 233a2076a867..84d42d42f834 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -18,16 +18,20 @@ package com.android.server.wm; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; +import static com.android.server.wm.utils.LastCallVerifier.lastCall; import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; import android.graphics.Rect; @@ -35,8 +39,9 @@ import android.platform.test.annotations.Presubmit; import android.view.SurfaceControl; import android.view.SurfaceSession; -import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.testutils.StubTransaction; +import com.android.server.wm.utils.MockAnimationAdapter; +import com.android.window.flags.Flags; import org.junit.Before; import org.junit.Test; @@ -118,102 +123,168 @@ public class DimmerTests extends WindowTestsBase { } } - private MockSurfaceBuildingContainer mHost; - private Dimmer mDimmer; - private SurfaceControl.Transaction mTransaction; - private Dimmer.SurfaceAnimatorStarter mSurfaceAnimatorStarter; + static class MockAnimationAdapterFactory extends SmoothDimmer.AnimationAdapterFactory { + public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec, + SurfaceAnimationRunner runner) { + return sTestAnimation; + } + } - private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter { + private static class SurfaceAnimatorStarterImpl implements LegacyDimmer.SurfaceAnimatorStarter { @Override public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, - AnimationAdapter anim, boolean hidden, @AnimationType int type) { + AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type) { surfaceAnimator.mStaticAnimationFinishedCallback.onAnimationFinished(type, anim); } } + private MockSurfaceBuildingContainer mHost; + private Dimmer mDimmer; + private SurfaceControl.Transaction mTransaction; + private TestWindowContainer mChild; + private static AnimationAdapter sTestAnimation; + private static LegacyDimmer.SurfaceAnimatorStarter sSurfaceAnimatorStarter; + @Before public void setUp() throws Exception { mHost = new MockSurfaceBuildingContainer(mWm); - mSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl()); mTransaction = spy(StubTransaction.class); - mDimmer = new Dimmer(mHost, mSurfaceAnimatorStarter); + mChild = new TestWindowContainer(mWm); + if (Flags.dimmerRefactor()) { + sTestAnimation = spy(new MockAnimationAdapter()); + mDimmer = new SmoothDimmer(mHost, new MockAnimationAdapterFactory()); + } else { + sSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl()); + mDimmer = new LegacyDimmer(mHost, sSurfaceAnimatorStarter); + } } @Test public void testUpdateDimsAppliesCrop() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); + mHost.addChild(mChild, 0); final float alpha = 0.8f; - mDimmer.dimAbove(child, alpha); + mDimmer.dimAbove(mChild, alpha); int width = 100; int height = 300; - mDimmer.mDimState.mDimBounds.set(0, 0, width, height); + mDimmer.getDimBounds().set(0, 0, width, height); mDimmer.updateDims(mTransaction); - verify(mTransaction).setWindowCrop(getDimLayer(), width, height); - verify(mTransaction).show(getDimLayer()); + verify(mTransaction).setWindowCrop(mDimmer.getDimLayer(), width, height); + verify(mTransaction).show(mDimmer.getDimLayer()); } @Test - public void testDimAboveWithChildCreatesSurfaceAboveChild() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); + public void testDimAboveWithChildCreatesSurfaceAboveChild_Smooth() { + assumeTrue(Flags.dimmerRefactor()); + final float alpha = 0.8f; + mHost.addChild(mChild, 0); + mDimmer.dimAbove(mChild, alpha); + SurfaceControl dimLayer = mDimmer.getDimLayer(); + + assertNotNull("Dimmer should have created a surface", dimLayer); + + mDimmer.updateDims(mTransaction); + verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction), + anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class)); + verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, 1); + verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha); + } + @Test + public void testDimAboveWithChildCreatesSurfaceAboveChild_Legacy() { + assumeFalse(Flags.dimmerRefactor()); final float alpha = 0.8f; - mDimmer.dimAbove(child, alpha); - SurfaceControl dimLayer = getDimLayer(); + mHost.addChild(mChild, 0); + mDimmer.dimAbove(mChild, alpha); + SurfaceControl dimLayer = mDimmer.getDimLayer(); assertNotNull("Dimmer should have created a surface", dimLayer); verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha); - verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, 1); + verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, 1); } @Test - public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); + public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() { + assumeTrue(Flags.dimmerRefactor()); + final float alpha = 0.7f; + mHost.addChild(mChild, 0); + mDimmer.dimBelow(mChild, alpha, 50); + SurfaceControl dimLayer = mDimmer.getDimLayer(); - final float alpha = 0.8f; - mDimmer.dimBelow(child, alpha, 0); - SurfaceControl dimLayer = getDimLayer(); + assertNotNull("Dimmer should have created a surface", dimLayer); + + mDimmer.updateDims(mTransaction); + verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction), + anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class)); + verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, -1); + verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha); + verify(mTransaction).setBackgroundBlurRadius(dimLayer, 50); + } + + @Test + public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() { + assumeFalse(Flags.dimmerRefactor()); + final float alpha = 0.7f; + mHost.addChild(mChild, 0); + mDimmer.dimBelow(mChild, alpha, 50); + SurfaceControl dimLayer = mDimmer.getDimLayer(); assertNotNull("Dimmer should have created a surface", dimLayer); verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha); - verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1); + verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1); } @Test - public void testDimBelowWithChildSurfaceDestroyedWhenReset() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); + public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() { + assumeTrue(Flags.dimmerRefactor()); + mHost.addChild(mChild, 0); + + final float alpha = 0.8f; + // Dim once + mDimmer.dimBelow(mChild, alpha, 0); + SurfaceControl dimLayer = mDimmer.getDimLayer(); + mDimmer.updateDims(mTransaction); + // Reset, and don't dim + mDimmer.resetDimStates(); + mDimmer.updateDims(mTransaction); + verify(mTransaction).show(dimLayer); + verify(mTransaction).remove(dimLayer); + } + + @Test + public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() { + assumeFalse(Flags.dimmerRefactor()); + mHost.addChild(mChild, 0); final float alpha = 0.8f; - mDimmer.dimAbove(child, alpha); - SurfaceControl dimLayer = getDimLayer(); + mDimmer.dimAbove(mChild, alpha); + SurfaceControl dimLayer = mDimmer.getDimLayer(); mDimmer.resetDimStates(); mDimmer.updateDims(mTransaction); - verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any( - SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(), + verify(sSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), + any(SurfaceControl.Transaction.class), any(AnimationAdapter.class), + anyBoolean(), eq(ANIMATION_TYPE_DIMMER)); verify(mHost.getPendingTransaction()).remove(dimLayer); } @Test public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); + mHost.addChild(mChild, 0); final float alpha = 0.8f; - mDimmer.dimAbove(child, alpha); - SurfaceControl dimLayer = getDimLayer(); + // Dim once + mDimmer.dimBelow(mChild, alpha, 0); + SurfaceControl dimLayer = mDimmer.getDimLayer(); + mDimmer.updateDims(mTransaction); + // Reset and dim again mDimmer.resetDimStates(); - mDimmer.dimAbove(child, alpha); - + mDimmer.dimAbove(mChild, alpha); mDimmer.updateDims(mTransaction); verify(mTransaction).show(dimLayer); verify(mTransaction, never()).remove(dimLayer); @@ -221,14 +292,12 @@ public class DimmerTests extends WindowTestsBase { @Test public void testDimUpdateWhileDimming() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); - + mHost.addChild(mChild, 0); final float alpha = 0.8f; - mDimmer.dimAbove(child, alpha); - final Rect bounds = mDimmer.mDimState.mDimBounds; + mDimmer.dimAbove(mChild, alpha); + final Rect bounds = mDimmer.getDimBounds(); - SurfaceControl dimLayer = getDimLayer(); + SurfaceControl dimLayer = mDimmer.getDimLayer(); bounds.set(0, 0, 10, 10); mDimmer.updateDims(mTransaction); verify(mTransaction).setWindowCrop(dimLayer, bounds.width(), bounds.height()); @@ -242,41 +311,56 @@ public class DimmerTests extends WindowTestsBase { } @Test - public void testRemoveDimImmediately() { - TestWindowContainer child = new TestWindowContainer(mWm); - mHost.addChild(child, 0); + public void testRemoveDimImmediately_Smooth() { + assumeTrue(Flags.dimmerRefactor()); + mHost.addChild(mChild, 0); + mDimmer.dimAbove(mChild, 1); + SurfaceControl dimLayer = mDimmer.getDimLayer(); + mDimmer.updateDims(mTransaction); + verify(mTransaction, times(1)).show(dimLayer); - mDimmer.dimAbove(child, 1); - SurfaceControl dimLayer = getDimLayer(); + reset(sTestAnimation); + mDimmer.dontAnimateExit(); + mDimmer.resetDimStates(); + mDimmer.updateDims(mTransaction); + verify(sTestAnimation, never()).startAnimation( + any(SurfaceControl.class), any(SurfaceControl.Transaction.class), + anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class)); + verify(mTransaction).remove(dimLayer); + } + + @Test + public void testRemoveDimImmediately_Legacy() { + assumeFalse(Flags.dimmerRefactor()); + mHost.addChild(mChild, 0); + mDimmer.dimAbove(mChild, 1); + SurfaceControl dimLayer = mDimmer.getDimLayer(); mDimmer.updateDims(mTransaction); verify(mTransaction, times(1)).show(dimLayer); - reset(mSurfaceAnimatorStarter); + reset(sSurfaceAnimatorStarter); mDimmer.dontAnimateExit(); mDimmer.resetDimStates(); mDimmer.updateDims(mTransaction); - verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any( - SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(), + verify(sSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), + any(SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(), eq(ANIMATION_TYPE_DIMMER)); verify(mTransaction).remove(dimLayer); } @Test - public void testDimmerWithBlurUpdatesTransaction() { + public void testDimmerWithBlurUpdatesTransaction_Legacy() { + assumeFalse(Flags.dimmerRefactor()); TestWindowContainer child = new TestWindowContainer(mWm); mHost.addChild(child, 0); final int blurRadius = 50; mDimmer.dimBelow(child, 0, blurRadius); - SurfaceControl dimLayer = getDimLayer(); + SurfaceControl dimLayer = mDimmer.getDimLayer(); assertNotNull("Dimmer should have created a surface", dimLayer); verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius); verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1); } - - private SurfaceControl getDimLayer() { - return mDimmer.mDimState.mDimLayer; - } } diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java new file mode 100644 index 000000000000..320d09444681 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 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 com.android.server.wm.utils; + +import org.mockito.internal.verification.VerificationModeFactory; +import org.mockito.internal.verification.api.VerificationData; +import org.mockito.invocation.Invocation; +import org.mockito.invocation.MatchableInvocation; +import org.mockito.verification.VerificationMode; + +import java.util.List; + +/** + * Verifier to check that the last call of a method received the expected argument + */ +public class LastCallVerifier implements VerificationMode { + + /** + * Allows comparing the expected invocation with the last invocation on the same method + */ + public static LastCallVerifier lastCall() { + return new LastCallVerifier(); + } + + @Override + public void verify(VerificationData data) { + List<Invocation> invocations = data.getAllInvocations(); + MatchableInvocation target = data.getTarget(); + for (int i = invocations.size() - 1; i >= 0; i--) { + final Invocation invocation = invocations.get(i); + if (target.hasSameMethod(invocation)) { + if (target.matches(invocation)) { + return; + } else { + throw new LastCallMismatch(target.getInvocation(), invocation, invocations); + } + } + } + throw new RuntimeException(target + " never invoked"); + } + + @Override + public VerificationMode description(String description) { + return VerificationModeFactory.description(this, description); + } + + static class LastCallMismatch extends RuntimeException { + LastCallMismatch( + Invocation expected, Invocation received, List<Invocation> allInvocations) { + super("Expected invocation " + expected + " but received " + received + + " as the last invocation.\nAll registered invocations:\n" + allInvocations); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java new file mode 100644 index 000000000000..1a66970a8dc2 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 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 com.android.server.wm.utils; + +import android.util.proto.ProtoOutputStream; +import android.view.SurfaceControl; + +import androidx.annotation.NonNull; + +import com.android.server.wm.AnimationAdapter; +import com.android.server.wm.SurfaceAnimator; + +import java.io.PrintWriter; + +/** + * An empty animation adapter which just executes the finish callback + */ +public class MockAnimationAdapter implements AnimationAdapter { + + @Override + public boolean getShowWallpaper() { + return false; + } + + @Override + public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, + int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + // As the animation won't run, finish it immediately + finishCallback.onAnimationFinished(0, null); + } + + @Override + public void onAnimationCancelled(SurfaceControl animationLeash) {} + + @Override + public long getDurationHint() { + return 0; + } + + @Override + public long getStatusBarTransitionsStartTime() { + return 0; + } + + @Override + public void dump(PrintWriter pw, String prefix) {} + + @Override + public void dumpDebug(ProtoOutputStream proto) {} +} |