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