1 /* 2 * Copyright (C) 2023 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.internal.protolog.WmProtoLogGroups.WM_DEBUG_DIMMER; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.graphics.Rect; 24 import android.util.Log; 25 import android.view.Surface; 26 import android.view.SurfaceControl; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.internal.protolog.ProtoLog; 30 import com.android.window.flags.Flags; 31 32 33 /** 34 * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is 35 * black layers of varying opacity at various Z-levels which create the effect of a Dim. 36 */ 37 class Dimmer { 38 39 /** 40 * The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the 41 * host, some controller of it, or one of the hosts children. 42 */ 43 private final WindowContainer<?> mHost; 44 45 private static final String TAG = "WindowManagerDimmer"; 46 47 DimState mDimState; 48 final DimmerAnimationHelper.AnimationAdapterFactory mAnimationAdapterFactory; 49 50 /** 51 * Controls the dim behaviour 52 */ 53 protected class DimState { 54 /** Related objects */ 55 SurfaceControl mDimSurface; 56 final WindowContainer<?> mHostContainer; 57 // The last container to request to dim 58 private WindowState mLastDimmingWindow; 59 /** Animation */ 60 private final DimmerAnimationHelper mAnimationHelper; 61 boolean mSkipAnimation = false; 62 // Determines whether the dim layer should animate before destroying. 63 boolean mAnimateExit = true; 64 /** Surface visibility and bounds */ 65 private boolean mIsVisible = false; 66 // TODO(b/64816140): Remove after confirming dimmer layer always matches its container. 67 final Rect mDimBounds = new Rect(); 68 DimState()69 DimState() { 70 mHostContainer = mHost; 71 mAnimationHelper = new DimmerAnimationHelper(mHost, mAnimationAdapterFactory); 72 try { 73 mDimSurface = makeDimLayer(); 74 EventLogTags.writeWmDimCreated(mHost.getName(), mDimSurface.getLayerId()); 75 } catch (Surface.OutOfResourcesException e) { 76 Log.w(TAG, "OutOfResourcesException creating dim surface"); 77 } 78 } 79 ensureVisible(@onNull SurfaceControl.Transaction t)80 void ensureVisible(@NonNull SurfaceControl.Transaction t) { 81 if (!mIsVisible) { 82 t.show(mDimSurface); 83 t.setAlpha(mDimSurface, 0f); 84 mIsVisible = true; 85 } 86 } 87 adjustSurfaceLayout(@onNull SurfaceControl.Transaction t)88 void adjustSurfaceLayout(@NonNull SurfaceControl.Transaction t) { 89 // TODO: Once we use geometry from hierarchy this falls away. 90 t.setPosition(mDimSurface, mDimBounds.left, mDimBounds.top); 91 t.setWindowCrop(mDimSurface, mDimBounds.width(), mDimBounds.height()); 92 } 93 94 /** 95 * Set the parameters to prepare the dim to change its appearance 96 */ prepareLookChange(float alpha, int blurRadius)97 void prepareLookChange(float alpha, int blurRadius) { 98 mAnimationHelper.setRequestedAppearance(alpha, blurRadius); 99 } 100 101 /** 102 * Prepare the dim for the exit animation 103 */ exit(@onNull SurfaceControl.Transaction t)104 void exit(@NonNull SurfaceControl.Transaction t) { 105 EventLogTags.writeWmDimExit(mDimState.mDimSurface.getLayerId(), 106 mDimState.mLastDimmingWindow != null 107 ? mDimState.mLastDimmingWindow.getName() : "-", 108 mDimState.mHostContainer.isVisible() ? 1 : 0, 109 mAnimateExit ? 0 : 1); 110 if (!mAnimateExit) { 111 remove(t); 112 } else { 113 mAnimationHelper.setExitParameters(); 114 setReady(t); 115 } 116 } 117 remove(@onNull SurfaceControl.Transaction t)118 void remove(@NonNull SurfaceControl.Transaction t) { 119 EventLogTags.writeWmDimCancelAnim(mDimSurface.getLayerId(), "ready to remove"); 120 mAnimationHelper.stopCurrentAnimation(mDimSurface); 121 if (mDimSurface.isValid()) { 122 EventLogTags.writeWmDimRemoved(mDimSurface.getLayerId()); 123 t.remove(mDimSurface); 124 ProtoLog.d(WM_DEBUG_DIMMER, 125 "Removing dim surface %s on transaction %s", this, t); 126 } else { 127 Log.w(TAG, "Tried to remove " + mDimSurface + " multiple times\n"); 128 } 129 } 130 131 @Override toString()132 public String toString() { 133 return "Dimmer#DimState with host=" + mHostContainer + ", surface=" + mDimSurface; 134 } 135 136 reasonForRemoving()137 String reasonForRemoving() { 138 return mLastDimmingWindow != null ? mLastDimmingWindow 139 + " is dimming but host " + mHostContainer + " is not visibleRequested" 140 : " no one is dimming"; 141 } 142 143 /** 144 * Set the parameters to prepare the dim to be relative parented to the dimming container 145 */ prepareReparent(@ullable WindowContainer<?> geometryParent, @NonNull WindowState relativeParent)146 void prepareReparent(@Nullable WindowContainer<?> geometryParent, 147 @NonNull WindowState relativeParent) { 148 mAnimationHelper.setRequestedRelativeParent(relativeParent); 149 mAnimationHelper.setRequestedGeometryParent(geometryParent); 150 } 151 152 /** 153 * Call when all the changes have been requested to have them applied 154 * @param t The transaction in which to apply the changes 155 */ setReady(@onNull SurfaceControl.Transaction t)156 void setReady(@NonNull SurfaceControl.Transaction t) { 157 mAnimationHelper.applyChanges(t, this); 158 } 159 160 /** 161 * Whether anyone is currently requesting the dim 162 */ isDimming()163 boolean isDimming() { 164 return mLastDimmingWindow != null 165 && (mHostContainer.isVisibleRequested() || !Flags.useTasksDimOnly()); 166 } 167 makeDimLayer()168 private SurfaceControl makeDimLayer() { 169 return mHost.makeChildSurface(null) 170 .setParent(mHost.getSurfaceControl()) 171 .setColorLayer() 172 .setName("Dim Layer for - " + mHost.getName()) 173 .setCallsite("DimLayer.makeDimLayer") 174 .build(); 175 } 176 } 177 Dimmer(@onNull WindowContainer<?> host)178 protected Dimmer(@NonNull WindowContainer<?> host) { 179 this(host, new DimmerAnimationHelper.AnimationAdapterFactory()); 180 } 181 182 @VisibleForTesting Dimmer(@onNull WindowContainer host, @NonNull DimmerAnimationHelper.AnimationAdapterFactory animationFactory)183 Dimmer(@NonNull WindowContainer host, 184 @NonNull DimmerAnimationHelper.AnimationAdapterFactory animationFactory) { 185 mHost = host; 186 mAnimationAdapterFactory = animationFactory; 187 } 188 hostIsTask()189 public boolean hostIsTask() { 190 return mHost.asTask() != null; 191 } 192 193 /** 194 * Mark all dims as pending completion on the next call to {@link #updateDims} 195 * 196 * Called before iterating on mHost's children, first step of dimming. 197 * This is intended for us by the host container, to be called at the beginning of 198 * {@link WindowContainer#prepareSurfaces}. After calling this, the container should 199 * chain {@link WindowContainer#prepareSurfaces} down to its children to give them 200 * a chance to request dims to continue. 201 */ resetDimStates()202 void resetDimStates() { 203 if (mDimState != null) { 204 mDimState.mLastDimmingWindow = null; 205 } 206 } 207 208 /** 209 * Set the aspect of the dim layer, and request to keep dimming. 210 * For each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset, and the 211 * child should call setAppearance again to request the Dim to continue. 212 * If multiple containers call this method, only the changes relative to the topmost will be 213 * applied. 214 * The creation of the dim layer is delayed if the requested dim and blur are 0. 215 * @param dimmingContainer Container requesting the dim 216 * @param alpha Dim amount 217 * @param blurRadius Blur amount 218 */ adjustAppearance(@onNull WindowState dimmingContainer, float alpha, int blurRadius)219 protected void adjustAppearance(@NonNull WindowState dimmingContainer, 220 float alpha, int blurRadius) { 221 if (!mHost.isVisibleRequested()) { 222 // If the host is already going away, there is no point in keeping dimming 223 return; 224 } 225 226 if (mDimState != null || (alpha != 0 || blurRadius != 0)) { 227 final DimState d = obtainDimState(dimmingContainer); 228 d.prepareLookChange(alpha, blurRadius); 229 } 230 } 231 232 /** 233 * Position the dim relatively to the dimming container. 234 * Normally called together with #setAppearance, it can be called alone to keep the dim parented 235 * to a visible container until the next dimming container is ready. 236 * If multiple containers call this method, only the changes relative to the topmost will be 237 * applied. 238 * 239 * For each call to {@link WindowContainer#prepareSurfaces()} the DimState will be reset, and 240 * the child of the host should call adjustRelativeLayer and {@link Dimmer#adjustAppearance} to 241 * continue dimming. Indeed, this method won't be able to keep dimming or get a new DimState 242 * without also adjusting the appearance. 243 * @param geometryParent The container that defines the geometry of the dim 244 * @param dimmingContainer The container that is dimming. The dim layer will be rel-z 245 * parented below it 246 */ adjustPosition(@ullable WindowContainer<?> geometryParent, @NonNull WindowState dimmingContainer)247 public void adjustPosition(@Nullable WindowContainer<?> geometryParent, 248 @NonNull WindowState dimmingContainer) { 249 if (mDimState != null) { 250 mDimState.prepareReparent(geometryParent, dimmingContainer); 251 } 252 } 253 254 /** 255 * Call after invoking {@link WindowContainer#prepareSurfaces} on children as 256 * described in {@link #resetDimStates}. 257 * 258 * @param t A transaction in which to update the dims. 259 * @return true if any Dims were updated. 260 */ updateDims(@onNull SurfaceControl.Transaction t)261 boolean updateDims(@NonNull SurfaceControl.Transaction t) { 262 if (mDimState == null) { 263 return false; 264 } 265 if (!mDimState.isDimming()) { 266 // No one is dimming, fade out and remove the dim 267 mDimState.exit(t); 268 mDimState = null; 269 return false; 270 } else { 271 // Someone is dimming, show the requested changes 272 if (!Flags.useTasksDimOnly()) { 273 mDimState.adjustSurfaceLayout(t); 274 } 275 if (!mDimState.mIsVisible && mDimState.mLastDimmingWindow != null 276 && mDimState.mLastDimmingWindow.mActivityRecord != null 277 && mDimState.mLastDimmingWindow.mActivityRecord.mStartingData != null) { 278 // Skip enter animation while starting window is on top of its activity 279 mDimState.mSkipAnimation = true; 280 } 281 mDimState.setReady(t); 282 return true; 283 } 284 } 285 hasDimState()286 boolean hasDimState() { 287 return mDimState != null; 288 } 289 isDimming()290 boolean isDimming() { 291 return mDimState != null && mDimState.isDimming(); 292 } 293 294 @NonNull obtainDimState(@onNull WindowState window)295 private DimState obtainDimState(@NonNull WindowState window) { 296 if (mDimState == null) { 297 mDimState = new DimState(); 298 } 299 mDimState.mLastDimmingWindow = window; 300 return mDimState; 301 } 302 303 /** Returns non-null bounds if the dimmer is showing. */ 304 @VisibleForTesting getDimLayer()305 SurfaceControl getDimLayer() { 306 return mDimState != null ? mDimState.mDimSurface : null; 307 } 308 getDimBounds()309 Rect getDimBounds() { 310 return mDimState != null ? mDimState.mDimBounds : null; 311 } 312 dontAnimateExit()313 void dontAnimateExit() { 314 if (mDimState != null) { 315 mDimState.mAnimateExit = false; 316 } 317 } 318 } 319