1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.wm; 18 19 import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS; 20 import static com.android.server.wm.AlphaAnimationSpecProto.FROM; 21 import static com.android.server.wm.AlphaAnimationSpecProto.TO; 22 import static com.android.server.wm.AnimationSpecProto.ALPHA; 23 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; 24 25 import android.graphics.Rect; 26 import android.util.Log; 27 import android.util.proto.ProtoOutputStream; 28 import android.view.Surface; 29 import android.view.SurfaceControl; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.server.wm.SurfaceAnimator.AnimationType; 33 34 import java.io.PrintWriter; 35 36 /** 37 * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is 38 * black layers of varying opacity at various Z-levels which create the effect of a Dim. 39 */ 40 class Dimmer { 41 private static final String TAG = "WindowManager"; 42 // This is in milliseconds. 43 private static final int DEFAULT_DIM_ANIM_DURATION = 200; 44 45 private class DimAnimatable implements SurfaceAnimator.Animatable { 46 private SurfaceControl mDimLayer; 47 DimAnimatable(SurfaceControl dimLayer)48 private DimAnimatable(SurfaceControl dimLayer) { 49 mDimLayer = dimLayer; 50 } 51 52 @Override getSyncTransaction()53 public SurfaceControl.Transaction getSyncTransaction() { 54 return mHost.getSyncTransaction(); 55 } 56 57 @Override getPendingTransaction()58 public SurfaceControl.Transaction getPendingTransaction() { 59 return mHost.getPendingTransaction(); 60 } 61 62 @Override commitPendingTransaction()63 public void commitPendingTransaction() { 64 mHost.commitPendingTransaction(); 65 } 66 67 @Override onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash)68 public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) { 69 } 70 71 @Override onAnimationLeashLost(SurfaceControl.Transaction t)72 public void onAnimationLeashLost(SurfaceControl.Transaction t) { 73 } 74 75 @Override makeAnimationLeash()76 public SurfaceControl.Builder makeAnimationLeash() { 77 return mHost.makeAnimationLeash(); 78 } 79 80 @Override getAnimationLeashParent()81 public SurfaceControl getAnimationLeashParent() { 82 return mHost.getSurfaceControl(); 83 } 84 85 @Override getSurfaceControl()86 public SurfaceControl getSurfaceControl() { 87 return mDimLayer; 88 } 89 90 @Override getParentSurfaceControl()91 public SurfaceControl getParentSurfaceControl() { 92 return mHost.getSurfaceControl(); 93 } 94 95 @Override getSurfaceWidth()96 public int getSurfaceWidth() { 97 // This will determine the size of the leash created. This should be the size of the 98 // host and not the dim layer since the dim layer may get bigger during animation. If 99 // that occurs, the leash size cannot change so we need to ensure the leash is big 100 // enough that the dim layer can grow. 101 // This works because the mHost will be a Task which has the display bounds. 102 return mHost.getSurfaceWidth(); 103 } 104 105 @Override getSurfaceHeight()106 public int getSurfaceHeight() { 107 // See getSurfaceWidth() above for explanation. 108 return mHost.getSurfaceHeight(); 109 } 110 removeSurface()111 void removeSurface() { 112 if (mDimLayer != null && mDimLayer.isValid()) { 113 getSyncTransaction().remove(mDimLayer); 114 } 115 mDimLayer = null; 116 } 117 } 118 119 @VisibleForTesting 120 class DimState { 121 /** 122 * The layer where property changes should be invoked on. 123 */ 124 SurfaceControl mDimLayer; 125 boolean mDimming; 126 boolean isVisible; 127 SurfaceAnimator mSurfaceAnimator; 128 129 // TODO(b/64816140): Remove after confirming dimmer layer always matches its container. 130 final Rect mDimBounds = new Rect(); 131 132 /** 133 * Determines whether the dim layer should animate before destroying. 134 */ 135 boolean mAnimateExit = true; 136 137 /** 138 * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for 139 * details on Dim lifecycle. 140 */ 141 boolean mDontReset; 142 DimState(SurfaceControl dimLayer)143 DimState(SurfaceControl dimLayer) { 144 mDimLayer = dimLayer; 145 mDimming = true; 146 final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer); 147 mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> { 148 if (!mDimming) { 149 dimAnimatable.removeSurface(); 150 } 151 }, mHost.mWmService); 152 } 153 } 154 155 /** 156 * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the 157 * host, some controller of it, or one of the hosts children. 158 */ 159 private WindowContainer mHost; 160 private WindowContainer mLastRequestedDimContainer; 161 @VisibleForTesting 162 DimState mDimState; 163 164 private final SurfaceAnimatorStarter mSurfaceAnimatorStarter; 165 166 @VisibleForTesting 167 interface SurfaceAnimatorStarter { startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, AnimationAdapter anim, boolean hidden, @AnimationType int type)168 void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, 169 AnimationAdapter anim, boolean hidden, @AnimationType int type); 170 } 171 Dimmer(WindowContainer host)172 Dimmer(WindowContainer host) { 173 this(host, SurfaceAnimator::startAnimation); 174 } 175 Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter)176 Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) { 177 mHost = host; 178 mSurfaceAnimatorStarter = surfaceAnimatorStarter; 179 } 180 getHost()181 WindowContainer<?> getHost() { 182 return mHost; 183 } 184 makeDimLayer()185 private SurfaceControl makeDimLayer() { 186 return mHost.makeChildSurface(null) 187 .setParent(mHost.getSurfaceControl()) 188 .setColorLayer() 189 .setName("Dim Layer for - " + mHost.getName()) 190 .setCallsite("Dimmer.makeDimLayer") 191 .build(); 192 } 193 194 /** 195 * Retrieve the DimState, creating one if it doesn't exist. 196 */ getDimState(WindowContainer container)197 private DimState getDimState(WindowContainer container) { 198 if (mDimState == null) { 199 try { 200 final SurfaceControl ctl = makeDimLayer(); 201 mDimState = new DimState(ctl); 202 /** 203 * See documentation on {@link #dimAbove} to understand lifecycle management of 204 * Dim's via state resetting for Dim's with containers. 205 */ 206 if (container == null) { 207 mDimState.mDontReset = true; 208 } 209 } catch (Surface.OutOfResourcesException e) { 210 Log.w(TAG, "OutOfResourcesException creating dim surface"); 211 } 212 } 213 214 mLastRequestedDimContainer = container; 215 return mDimState; 216 } 217 dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius)218 private void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) { 219 final DimState d = getDimState(container); 220 221 if (d == null) { 222 return; 223 } 224 225 // The dim method is called from WindowState.prepareSurfaces(), which is always called 226 // in the correct Z from lowest Z to highest. This ensures that the dim layer is always 227 // relative to the highest Z layer with a dim. 228 SurfaceControl.Transaction t = mHost.getPendingTransaction(); 229 t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); 230 t.setAlpha(d.mDimLayer, alpha); 231 t.setBackgroundBlurRadius(d.mDimLayer, blurRadius); 232 233 d.mDimming = true; 234 } 235 236 /** 237 * Place a dim above the given container, which should be a child of the host container. 238 * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset 239 * and the child should call dimAbove again to request the Dim to continue. 240 * 241 * @param container The container which to dim above. Should be a child of our host. 242 * @param alpha The alpha at which to Dim. 243 */ dimAbove(WindowContainer container, float alpha)244 void dimAbove(WindowContainer container, float alpha) { 245 dim(container, 1, alpha, 0); 246 } 247 248 /** 249 * Like {@link #dimAbove} but places the dim below the given container. 250 * 251 * @param container The container which to dim below. Should be a child of our host. 252 * @param alpha The alpha at which to Dim. 253 * @param blurRadius The amount of blur added to the Dim. 254 */ 255 dimBelow(WindowContainer container, float alpha, int blurRadius)256 void dimBelow(WindowContainer container, float alpha, int blurRadius) { 257 dim(container, -1, alpha, blurRadius); 258 } 259 260 /** 261 * Mark all dims as pending completion on the next call to {@link #updateDims} 262 * 263 * This is intended for us by the host container, to be called at the beginning of 264 * {@link WindowContainer#prepareSurfaces}. After calling this, the container should 265 * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them 266 * a chance to request dims to continue. 267 */ resetDimStates()268 void resetDimStates() { 269 if (mDimState == null) { 270 return; 271 } 272 if (!mDimState.mDontReset) { 273 mDimState.mDimming = false; 274 } 275 } 276 277 /** Returns non-null bounds if the dimmer is showing. */ getDimBounds()278 Rect getDimBounds() { 279 return mDimState != null ? mDimState.mDimBounds : null; 280 } 281 dontAnimateExit()282 void dontAnimateExit() { 283 if (mDimState != null) { 284 mDimState.mAnimateExit = false; 285 } 286 } 287 288 /** 289 * Call after invoking {@link WindowContainer#prepareSurfaces} on children as 290 * described in {@link #resetDimStates}. The dim bounds returned by {@link #resetDimStates} 291 * should be set before calling this method. 292 * 293 * @param t A transaction in which to update the dims. 294 * @return true if any Dims were updated. 295 */ updateDims(SurfaceControl.Transaction t)296 boolean updateDims(SurfaceControl.Transaction t) { 297 if (mDimState == null) { 298 return false; 299 } 300 301 if (!mDimState.mDimming) { 302 if (!mDimState.mAnimateExit) { 303 if (mDimState.mDimLayer.isValid()) { 304 t.remove(mDimState.mDimLayer); 305 } 306 } else { 307 startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t); 308 } 309 mDimState = null; 310 return false; 311 } else { 312 final Rect bounds = mDimState.mDimBounds; 313 // TODO: Once we use geometry from hierarchy this falls away. 314 t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top); 315 t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height()); 316 if (!mDimState.isVisible) { 317 mDimState.isVisible = true; 318 t.show(mDimState.mDimLayer); 319 startDimEnter(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t); 320 } 321 return true; 322 } 323 } 324 startDimEnter(WindowContainer container, SurfaceAnimator animator, SurfaceControl.Transaction t)325 private void startDimEnter(WindowContainer container, SurfaceAnimator animator, 326 SurfaceControl.Transaction t) { 327 startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */); 328 } 329 startDimExit(WindowContainer container, SurfaceAnimator animator, SurfaceControl.Transaction t)330 private void startDimExit(WindowContainer container, SurfaceAnimator animator, 331 SurfaceControl.Transaction t) { 332 startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */); 333 } 334 startAnim(WindowContainer container, SurfaceAnimator animator, SurfaceControl.Transaction t, float startAlpha, float endAlpha)335 private void startAnim(WindowContainer container, SurfaceAnimator animator, 336 SurfaceControl.Transaction t, float startAlpha, float endAlpha) { 337 mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter( 338 new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)), 339 mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */, 340 ANIMATION_TYPE_DIMMER); 341 } 342 getDimDuration(WindowContainer container)343 private long getDimDuration(WindowContainer container) { 344 // If there's no container, then there isn't an animation occurring while dimming. Set the 345 // duration to 0 so it immediately dims to the set alpha. 346 if (container == null) { 347 return 0; 348 } 349 350 // Otherwise use the same duration as the animation on the WindowContainer 351 AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); 352 return animationAdapter == null ? DEFAULT_DIM_ANIM_DURATION 353 : animationAdapter.getDurationHint(); 354 } 355 356 private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec { 357 private final long mDuration; 358 private final float mFromAlpha; 359 private final float mToAlpha; 360 AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration)361 AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) { 362 mFromAlpha = fromAlpha; 363 mToAlpha = toAlpha; 364 mDuration = duration; 365 } 366 367 @Override getDuration()368 public long getDuration() { 369 return mDuration; 370 } 371 372 @Override apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime)373 public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) { 374 final float fraction = getFraction(currentPlayTime); 375 final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha; 376 t.setAlpha(sc, alpha); 377 } 378 379 @Override dump(PrintWriter pw, String prefix)380 public void dump(PrintWriter pw, String prefix) { 381 pw.print(prefix); pw.print("from="); pw.print(mFromAlpha); 382 pw.print(" to="); pw.print(mToAlpha); 383 pw.print(" duration="); pw.println(mDuration); 384 } 385 386 @Override dumpDebugInner(ProtoOutputStream proto)387 public void dumpDebugInner(ProtoOutputStream proto) { 388 final long token = proto.start(ALPHA); 389 proto.write(FROM, mFromAlpha); 390 proto.write(TO, mToAlpha); 391 proto.write(DURATION_MS, mDuration); 392 proto.end(token); 393 } 394 } 395 } 396