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 getPendingTransaction()53 public SurfaceControl.Transaction getPendingTransaction() { 54 return mHost.getSyncTransaction(); 55 } 56 57 @Override commitPendingTransaction()58 public void commitPendingTransaction() { 59 mHost.commitPendingTransaction(); 60 } 61 62 @Override onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash)63 public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) { 64 } 65 66 @Override onAnimationLeashLost(SurfaceControl.Transaction t)67 public void onAnimationLeashLost(SurfaceControl.Transaction t) { 68 } 69 70 @Override makeAnimationLeash()71 public SurfaceControl.Builder makeAnimationLeash() { 72 return mHost.makeAnimationLeash(); 73 } 74 75 @Override getAnimationLeashParent()76 public SurfaceControl getAnimationLeashParent() { 77 return mHost.getSurfaceControl(); 78 } 79 80 @Override getSurfaceControl()81 public SurfaceControl getSurfaceControl() { 82 return mDimLayer; 83 } 84 85 @Override getParentSurfaceControl()86 public SurfaceControl getParentSurfaceControl() { 87 return mHost.getSurfaceControl(); 88 } 89 90 @Override getSurfaceWidth()91 public int getSurfaceWidth() { 92 // This will determine the size of the leash created. This should be the size of the 93 // host and not the dim layer since the dim layer may get bigger during animation. If 94 // that occurs, the leash size cannot change so we need to ensure the leash is big 95 // enough that the dim layer can grow. 96 // This works because the mHost will be a Task which has the display bounds. 97 return mHost.getSurfaceWidth(); 98 } 99 100 @Override getSurfaceHeight()101 public int getSurfaceHeight() { 102 // See getSurfaceWidth() above for explanation. 103 return mHost.getSurfaceHeight(); 104 } 105 removeSurface()106 void removeSurface() { 107 if (mDimLayer != null && mDimLayer.isValid()) { 108 getPendingTransaction().remove(mDimLayer); 109 } 110 mDimLayer = null; 111 } 112 } 113 114 @VisibleForTesting 115 class DimState { 116 /** 117 * The layer where property changes should be invoked on. 118 */ 119 SurfaceControl mDimLayer; 120 boolean mDimming; 121 boolean isVisible; 122 SurfaceAnimator mSurfaceAnimator; 123 124 /** 125 * Determines whether the dim layer should animate before destroying. 126 */ 127 boolean mAnimateExit = true; 128 129 /** 130 * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for 131 * details on Dim lifecycle. 132 */ 133 boolean mDontReset; 134 DimState(SurfaceControl dimLayer)135 DimState(SurfaceControl dimLayer) { 136 mDimLayer = dimLayer; 137 mDimming = true; 138 final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer); 139 mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> { 140 if (!mDimming) { 141 dimAnimatable.removeSurface(); 142 } 143 }, mHost.mWmService); 144 } 145 } 146 147 /** 148 * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the 149 * host, some controller of it, or one of the hosts children. 150 */ 151 private WindowContainer mHost; 152 private WindowContainer mLastRequestedDimContainer; 153 @VisibleForTesting 154 DimState mDimState; 155 156 private final SurfaceAnimatorStarter mSurfaceAnimatorStarter; 157 158 @VisibleForTesting 159 interface SurfaceAnimatorStarter { startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, AnimationAdapter anim, boolean hidden, @AnimationType int type)160 void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, 161 AnimationAdapter anim, boolean hidden, @AnimationType int type); 162 } 163 Dimmer(WindowContainer host)164 Dimmer(WindowContainer host) { 165 this(host, SurfaceAnimator::startAnimation); 166 } 167 Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter)168 Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) { 169 mHost = host; 170 mSurfaceAnimatorStarter = surfaceAnimatorStarter; 171 } 172 makeDimLayer()173 private SurfaceControl makeDimLayer() { 174 return mHost.makeChildSurface(null) 175 .setParent(mHost.getSurfaceControl()) 176 .setColorLayer() 177 .setName("Dim Layer for - " + mHost.getName()) 178 .setCallsite("Dimmer.makeDimLayer") 179 .build(); 180 } 181 182 /** 183 * Retrieve the DimState, creating one if it doesn't exist. 184 */ getDimState(WindowContainer container)185 private DimState getDimState(WindowContainer container) { 186 if (mDimState == null) { 187 try { 188 final SurfaceControl ctl = makeDimLayer(); 189 mDimState = new DimState(ctl); 190 /** 191 * See documentation on {@link #dimAbove} to understand lifecycle management of 192 * Dim's via state resetting for Dim's with containers. 193 */ 194 if (container == null) { 195 mDimState.mDontReset = true; 196 } 197 } catch (Surface.OutOfResourcesException e) { 198 Log.w(TAG, "OutOfResourcesException creating dim surface"); 199 } 200 } 201 202 mLastRequestedDimContainer = container; 203 return mDimState; 204 } 205 dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer, float alpha, int blurRadius)206 private void dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer, 207 float alpha, int blurRadius) { 208 final DimState d = getDimState(container); 209 210 if (d == null) { 211 return; 212 } 213 214 if (container != null) { 215 // The dim method is called from WindowState.prepareSurfaces(), which is always called 216 // in the correct Z from lowest Z to highest. This ensures that the dim layer is always 217 // relative to the highest Z layer with a dim. 218 t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); 219 } else { 220 t.setLayer(d.mDimLayer, Integer.MAX_VALUE); 221 } 222 t.setAlpha(d.mDimLayer, alpha); 223 t.setBackgroundBlurRadius(d.mDimLayer, blurRadius); 224 225 d.mDimming = true; 226 } 227 228 /** 229 * Finish a dim started by dimAbove in the case there was no call to dimAbove. 230 * 231 * @param t A Transaction in which to finish the dim. 232 */ stopDim(SurfaceControl.Transaction t)233 void stopDim(SurfaceControl.Transaction t) { 234 if (mDimState != null) { 235 t.hide(mDimState.mDimLayer); 236 mDimState.isVisible = false; 237 mDimState.mDontReset = false; 238 } 239 } 240 241 /** 242 * Place a Dim above the entire host container. The caller is responsible for calling stopDim to 243 * remove this effect. If the Dim can be assosciated with a particular child of the host 244 * consider using the other variant of dimAbove which ties the Dim lifetime to the child 245 * lifetime more explicitly. 246 * 247 * @param t A transaction in which to apply the Dim. 248 * @param alpha The alpha at which to Dim. 249 */ dimAbove(SurfaceControl.Transaction t, float alpha)250 void dimAbove(SurfaceControl.Transaction t, float alpha) { 251 dim(t, null, 1, alpha, 0); 252 } 253 254 /** 255 * Place a dim above the given container, which should be a child of the host container. 256 * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset 257 * and the child should call dimAbove again to request the Dim to continue. 258 * 259 * @param t A transaction in which to apply the Dim. 260 * @param container The container which to dim above. Should be a child of our host. 261 * @param alpha The alpha at which to Dim. 262 */ dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha)263 void dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha) { 264 dim(t, container, 1, alpha, 0); 265 } 266 267 /** 268 * Like {@link #dimAbove} but places the dim below the given container. 269 * 270 * @param t A transaction in which to apply the Dim. 271 * @param container The container which to dim below. Should be a child of our host. 272 * @param alpha The alpha at which to Dim. 273 * @param blurRadius The amount of blur added to the Dim. 274 */ 275 dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha, int blurRadius)276 void dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha, 277 int blurRadius) { 278 dim(t, container, -1, alpha, blurRadius); 279 } 280 281 /** 282 * Mark all dims as pending completion on the next call to {@link #updateDims} 283 * 284 * This is intended for us by the host container, to be called at the beginning of 285 * {@link WindowContainer#prepareSurfaces}. After calling this, the container should 286 * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them 287 * a chance to request dims to continue. 288 */ resetDimStates()289 void resetDimStates() { 290 if (mDimState != null && !mDimState.mDontReset) { 291 mDimState.mDimming = false; 292 } 293 } 294 dontAnimateExit()295 void dontAnimateExit() { 296 if (mDimState != null) { 297 mDimState.mAnimateExit = false; 298 } 299 } 300 301 /** 302 * Call after invoking {@link WindowContainer#prepareSurfaces} on children as 303 * described in {@link #resetDimStates}. 304 * 305 * @param t A transaction in which to update the dims. 306 * @param bounds The bounds at which to dim. 307 * @return true if any Dims were updated. 308 */ updateDims(SurfaceControl.Transaction t, Rect bounds)309 boolean updateDims(SurfaceControl.Transaction t, Rect bounds) { 310 if (mDimState == null) { 311 return false; 312 } 313 314 if (!mDimState.mDimming) { 315 if (!mDimState.mAnimateExit) { 316 if (mDimState.mDimLayer.isValid()) { 317 t.remove(mDimState.mDimLayer); 318 } 319 } else { 320 startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t); 321 } 322 mDimState = null; 323 return false; 324 } else { 325 // TODO: Once we use geometry from hierarchy this falls away. 326 t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top); 327 t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height()); 328 if (!mDimState.isVisible) { 329 mDimState.isVisible = true; 330 t.show(mDimState.mDimLayer); 331 startDimEnter(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t); 332 } 333 return true; 334 } 335 } 336 startDimEnter(WindowContainer container, SurfaceAnimator animator, SurfaceControl.Transaction t)337 private void startDimEnter(WindowContainer container, SurfaceAnimator animator, 338 SurfaceControl.Transaction t) { 339 startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */); 340 } 341 startDimExit(WindowContainer container, SurfaceAnimator animator, SurfaceControl.Transaction t)342 private void startDimExit(WindowContainer container, SurfaceAnimator animator, 343 SurfaceControl.Transaction t) { 344 startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */); 345 } 346 startAnim(WindowContainer container, SurfaceAnimator animator, SurfaceControl.Transaction t, float startAlpha, float endAlpha)347 private void startAnim(WindowContainer container, SurfaceAnimator animator, 348 SurfaceControl.Transaction t, float startAlpha, float endAlpha) { 349 mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter( 350 new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)), 351 mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */, 352 ANIMATION_TYPE_DIMMER); 353 } 354 getDimDuration(WindowContainer container)355 private long getDimDuration(WindowContainer container) { 356 // If there's no container, then there isn't an animation occurring while dimming. Set the 357 // duration to 0 so it immediately dims to the set alpha. 358 if (container == null) { 359 return 0; 360 } 361 362 // Otherwise use the same duration as the animation on the WindowContainer 363 AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); 364 return animationAdapter == null ? DEFAULT_DIM_ANIM_DURATION 365 : animationAdapter.getDurationHint(); 366 } 367 368 private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec { 369 private final long mDuration; 370 private final float mFromAlpha; 371 private final float mToAlpha; 372 AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration)373 AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) { 374 mFromAlpha = fromAlpha; 375 mToAlpha = toAlpha; 376 mDuration = duration; 377 } 378 379 @Override getDuration()380 public long getDuration() { 381 return mDuration; 382 } 383 384 @Override apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime)385 public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) { 386 final float fraction = getFraction(currentPlayTime); 387 final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha; 388 t.setAlpha(sc, alpha); 389 } 390 391 @Override dump(PrintWriter pw, String prefix)392 public void dump(PrintWriter pw, String prefix) { 393 pw.print(prefix); pw.print("from="); pw.print(mFromAlpha); 394 pw.print(" to="); pw.print(mToAlpha); 395 pw.print(" duration="); pw.println(mDuration); 396 } 397 398 @Override dumpDebugInner(ProtoOutputStream proto)399 public void dumpDebugInner(ProtoOutputStream proto) { 400 final long token = proto.start(ALPHA); 401 proto.write(FROM, mFromAlpha); 402 proto.write(TO, mToAlpha); 403 proto.write(DURATION_MS, mDuration); 404 proto.end(token); 405 } 406 } 407 } 408