/* * Copyright (C) 2017 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 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; /** * 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 final SurfaceControl mDimLayer; private DimAnimatable(SurfaceControl dimLayer) { mDimLayer = dimLayer; } @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 onAnimationLeashDestroyed(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(); } } @VisibleForTesting class DimState { /** * The layer where property changes should be invoked on. */ SurfaceControl mDimLayer; boolean mDimming; boolean isVisible; SurfaceAnimator mSurfaceAnimator; /** * 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; mSurfaceAnimator = new SurfaceAnimator(new DimAnimatable(dimLayer), () -> { if (!mDimming) { mDimLayer.destroy(); } }, mHost.mService); } } /** * The {@link WindowContainer} that our Dim's 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); } Dimmer(WindowContainer host) { this(host, SurfaceAnimator::startAnimation); } Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) { mHost = host; mSurfaceAnimatorStarter = surfaceAnimatorStarter; } private SurfaceControl makeDimLayer() { return mHost.makeChildSurface(null) .setParent(mHost.getSurfaceControl()) .setColorLayer(true) .setName("Dim Layer for - " + mHost.getName()) .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); /** * See documentation on {@link #dimAbove} to understand lifecycle management of * Dim's via state resetting for Dim's with containers. */ if (container == null) { mDimState.mDontReset = true; } } catch (Surface.OutOfResourcesException e) { Log.w(TAG, "OutOfResourcesException creating dim surface"); } } mLastRequestedDimContainer = container; return mDimState; } private void dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer, float alpha) { final DimState d = getDimState(container); if (d == null) { return; } if (container != null) { // 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. t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); } else { t.setLayer(d.mDimLayer, Integer.MAX_VALUE); } t.setAlpha(d.mDimLayer, alpha); d.mDimming = true; } /** * Finish a dim started by dimAbove in the case there was no call to dimAbove. * * @param t A Transaction in which to finish the dim. */ void stopDim(SurfaceControl.Transaction t) { if (mDimState != null) { t.hide(mDimState.mDimLayer); mDimState.isVisible = false; mDimState.mDontReset = false; } } /** * Place a Dim above the entire host container. The caller is responsible for calling stopDim to * remove this effect. If the Dim can be assosciated with a particular child of the host * consider using the other variant of dimAbove which ties the Dim lifetime to the child * lifetime more explicitly. * * @param t A transaction in which to apply the Dim. * @param alpha The alpha at which to Dim. */ void dimAbove(SurfaceControl.Transaction t, float alpha) { dim(t, null, 1, alpha); } /** * Place a dim above the given container, which should be a child of the host container. * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset * and the child should call dimAbove again to request the Dim to continue. * * @param t A transaction in which to apply the Dim. * @param container The container which to dim above. Should be a child of our host. * @param alpha The alpha at which to Dim. */ void dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha) { dim(t, container, 1, alpha); } /** * Like {@link #dimAbove} but places the dim below the given container. * * @param t A transaction in which to apply the Dim. * @param container The container which to dim below. Should be a child of our host. * @param alpha The alpha at which to Dim. */ void dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha) { dim(t, container, -1, alpha); } /** * Mark all dims as pending completion on the next call to {@link #updateDims} * * This is intended for us by the host container, to be called at the beginning of * {@link WindowContainer#prepareSurfaces}. After calling this, the container should * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them * a chance to request dims to continue. */ void resetDimStates() { if (mDimState != null && !mDimState.mDontReset) { mDimState.mDimming = false; } } void dontAnimateExit() { if (mDimState != null) { mDimState.mAnimateExit = false; } } /** * Call after invoking {@link WindowContainer#prepareSurfaces} on children as * described in {@link #resetDimStates}. * * @param t A transaction in which to update the dims. * @param bounds The bounds at which to dim. * @return true if any Dims were updated. */ boolean updateDims(SurfaceControl.Transaction t, Rect bounds) { if (mDimState == null) { return false; } if (!mDimState.mDimming) { if (!mDimState.mAnimateExit) { t.destroy(mDimState.mDimLayer); } else { startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t); } mDimState = null; return false; } else { // TODO: Once we use geometry from hierarchy this falls away. t.setSize(mDimState.mDimLayer, bounds.width(), bounds.height()); t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top); if (!mDimState.isVisible) { mDimState.isVisible = true; t.show(mDimState.mDimLayer); 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.mService.mSurfaceAnimationRunner), false /* hidden */); } 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(); return animationAdapter == null ? DEFAULT_DIM_ANIM_DURATION : 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) { float alpha = ((float) currentPlayTime / getDuration()) * (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 writeToProtoInner(ProtoOutputStream proto) { final long token = proto.start(ALPHA); proto.write(FROM, mFromAlpha); proto.write(TO, mToAlpha); proto.write(DURATION_MS, mDuration); proto.end(token); } } }