1 package com.android.server.wm; 2 3 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; 4 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER; 5 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 6 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 7 import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM; 8 9 import android.graphics.Rect; 10 import android.util.ArrayMap; 11 import android.util.Slog; 12 import android.util.TypedValue; 13 14 import com.android.internal.annotations.VisibleForTesting; 15 import com.android.server.wm.DimLayer.DimLayerUser; 16 17 import java.io.PrintWriter; 18 19 /** 20 * Centralizes the control of dim layers used for 21 * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND} 22 * as well as other use cases (such as dimming above a dead window). 23 */ 24 class DimLayerController { 25 private static final String TAG_LOCAL = "DimLayerController"; 26 private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM; 27 28 /** Amount of time in milliseconds to animate the dim surface from one value to another, 29 * when no window animation is driving it. */ 30 private static final int DEFAULT_DIM_DURATION = 200; 31 32 /** 33 * The default amount of dim applied over a dead window 34 */ 35 private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f; 36 37 // Shared dim layer for fullscreen users. {@link DimLayerState#dimLayer} will point to this 38 // instead of creating a new object per fullscreen task on a display. 39 private DimLayer mSharedFullScreenDimLayer; 40 41 private ArrayMap<DimLayer.DimLayerUser, DimLayerState> mState = new ArrayMap<>(); 42 43 private DisplayContent mDisplayContent; 44 45 private Rect mTmpBounds = new Rect(); 46 DimLayerController(DisplayContent displayContent)47 DimLayerController(DisplayContent displayContent) { 48 mDisplayContent = displayContent; 49 } 50 51 /** Updates the dim layer bounds, recreating it if needed. */ updateDimLayer(DimLayer.DimLayerUser dimLayerUser)52 void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) { 53 final DimLayerState state = getOrCreateDimLayerState(dimLayerUser); 54 final boolean previousFullscreen = state.dimLayer != null 55 && state.dimLayer == mSharedFullScreenDimLayer; 56 DimLayer newDimLayer; 57 final int displayId = mDisplayContent.getDisplayId(); 58 if (dimLayerUser.dimFullscreen()) { 59 if (previousFullscreen && mSharedFullScreenDimLayer != null) { 60 // Update the bounds for fullscreen in case of rotation. 61 mSharedFullScreenDimLayer.setBoundsForFullscreen(); 62 return; 63 } 64 // Use shared fullscreen dim layer 65 newDimLayer = mSharedFullScreenDimLayer; 66 if (newDimLayer == null) { 67 if (state.dimLayer != null) { 68 // Re-purpose the previous dim layer. 69 newDimLayer = state.dimLayer; 70 } else { 71 // Create new full screen dim layer. 72 newDimLayer = new DimLayer(mDisplayContent.mService, dimLayerUser, displayId, 73 getDimLayerTag(dimLayerUser)); 74 } 75 dimLayerUser.getDimBounds(mTmpBounds); 76 newDimLayer.setBounds(mTmpBounds); 77 mSharedFullScreenDimLayer = newDimLayer; 78 } else if (state.dimLayer != null) { 79 state.dimLayer.destroySurface(); 80 } 81 } else { 82 newDimLayer = (state.dimLayer == null || previousFullscreen) 83 ? new DimLayer(mDisplayContent.mService, dimLayerUser, displayId, 84 getDimLayerTag(dimLayerUser)) 85 : state.dimLayer; 86 dimLayerUser.getDimBounds(mTmpBounds); 87 newDimLayer.setBounds(mTmpBounds); 88 } 89 state.dimLayer = newDimLayer; 90 } 91 getDimLayerTag(DimLayerUser dimLayerUser)92 private static String getDimLayerTag(DimLayerUser dimLayerUser) { 93 return TAG_LOCAL + "/" + dimLayerUser.toShortString(); 94 } 95 getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser)96 private DimLayerState getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser) { 97 if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser=" 98 + dimLayerUser.toShortString()); 99 DimLayerState state = mState.get(dimLayerUser); 100 if (state == null) { 101 state = new DimLayerState(); 102 mState.put(dimLayerUser, state); 103 } 104 return state; 105 } 106 setContinueDimming(DimLayer.DimLayerUser dimLayerUser)107 private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) { 108 DimLayerState state = mState.get(dimLayerUser); 109 if (state == null) { 110 if (DEBUG_DIM_LAYER) Slog.w(TAG, "setContinueDimming, no state for: " 111 + dimLayerUser.toShortString()); 112 return; 113 } 114 state.continueDimming = true; 115 } 116 isDimming()117 boolean isDimming() { 118 for (int i = mState.size() - 1; i >= 0; i--) { 119 DimLayerState state = mState.valueAt(i); 120 if (state.dimLayer != null && state.dimLayer.isDimming()) { 121 return true; 122 } 123 } 124 return false; 125 } 126 resetDimming()127 void resetDimming() { 128 for (int i = mState.size() - 1; i >= 0; i--) { 129 mState.valueAt(i).continueDimming = false; 130 } 131 } 132 getContinueDimming(DimLayer.DimLayerUser dimLayerUser)133 private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) { 134 DimLayerState state = mState.get(dimLayerUser); 135 return state != null && state.continueDimming; 136 } 137 startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator newWinAnimator, boolean aboveApp)138 void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser, 139 WindowStateAnimator newWinAnimator, boolean aboveApp) { 140 // Only set dim params on the highest dimmed layer. 141 // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer. 142 DimLayerState state = getOrCreateDimLayerState(dimLayerUser); 143 state.dimAbove = aboveApp; 144 if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded," 145 + " dimLayerUser=" + dimLayerUser.toShortString() 146 + " newWinAnimator=" + newWinAnimator 147 + " state.animator=" + state.animator); 148 if (newWinAnimator.getShown() && (state.animator == null 149 || !state.animator.getShown() 150 || state.animator.mAnimLayer <= newWinAnimator.mAnimLayer)) { 151 state.animator = newWinAnimator; 152 if (state.animator.mWin.mAppToken == null && !dimLayerUser.dimFullscreen()) { 153 // Dim should cover the entire screen for system windows. 154 mDisplayContent.getLogicalDisplayRect(mTmpBounds); 155 } else { 156 dimLayerUser.getDimBounds(mTmpBounds); 157 } 158 state.dimLayer.setBounds(mTmpBounds); 159 } 160 } 161 stopDimmingIfNeeded()162 void stopDimmingIfNeeded() { 163 if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded, mState.size()=" + mState.size()); 164 for (int i = mState.size() - 1; i >= 0; i--) { 165 DimLayer.DimLayerUser dimLayerUser = mState.keyAt(i); 166 stopDimmingIfNeeded(dimLayerUser); 167 } 168 } 169 stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser)170 private void stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser) { 171 // No need to check if state is null, we know the key has a value. 172 DimLayerState state = mState.get(dimLayerUser); 173 if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded," 174 + " dimLayerUser=" + dimLayerUser.toShortString() 175 + " state.continueDimming=" + state.continueDimming 176 + " state.dimLayer.isDimming=" + state.dimLayer.isDimming()); 177 if (state.animator != null && state.animator.mWin.mWillReplaceWindow) { 178 return; 179 } 180 181 if (!state.continueDimming && state.dimLayer.isDimming()) { 182 state.animator = null; 183 dimLayerUser.getDimBounds(mTmpBounds); 184 state.dimLayer.setBounds(mTmpBounds); 185 } 186 } 187 animateDimLayers()188 boolean animateDimLayers() { 189 int fullScreen = -1; 190 int fullScreenAndDimming = -1; 191 int topFullScreenUserLayer = 0; 192 boolean result = false; 193 194 for (int i = mState.size() - 1; i >= 0; i--) { 195 final DimLayer.DimLayerUser user = mState.keyAt(i); 196 final DimLayerState state = mState.valueAt(i); 197 198 if (!user.isAttachedToDisplay()) { 199 // Leaked dim user that is no longer attached to the display. Go ahead and clean it 200 // clean-up and log what happened. 201 // TODO: This is a work around for b/34395537 as the dim user should have cleaned-up 202 // it self when it was detached from the display. Need to investigate how the dim 203 // user is leaking... 204 //Slog.wtfStack(TAG_WM, "Leaked dim user=" + user.toShortString() 205 // + " state=" + state); 206 Slog.w(TAG_WM, "Leaked dim user=" + user.toShortString() + " state=" + state); 207 removeDimLayerUser(user); 208 continue; 209 } 210 211 // We have to check that we are actually the shared fullscreen layer 212 // for this path. If we began as non fullscreen and became fullscreen 213 // (e.g. Docked stack closing), then we may not be the shared layer 214 // and we have to make sure we always animate the layer. 215 if (user.dimFullscreen() && state.dimLayer == mSharedFullScreenDimLayer) { 216 fullScreen = i; 217 if (!state.continueDimming) { 218 continue; 219 } 220 221 // When choosing which user to assign the shared fullscreen layer to 222 // we need to look at Z-order. 223 if (topFullScreenUserLayer == 0 || 224 (state.animator != null && state.animator.mAnimLayer > topFullScreenUserLayer)) { 225 fullScreenAndDimming = i; 226 if (state.animator != null) { 227 topFullScreenUserLayer = state.animator.mAnimLayer; 228 } 229 } 230 } else { 231 // We always want to animate the non fullscreen windows, they don't share their 232 // dim layers. 233 result |= animateDimLayers(user); 234 } 235 } 236 // For the shared, full screen dim layer, we prefer the animation that is causing it to 237 // appear. 238 if (fullScreenAndDimming != -1) { 239 result |= animateDimLayers(mState.keyAt(fullScreenAndDimming)); 240 } else if (fullScreen != -1) { 241 // If there is no animation for the full screen dim layer to appear, we can use any of 242 // the animators that will cause it to disappear. 243 result |= animateDimLayers(mState.keyAt(fullScreen)); 244 } 245 return result; 246 } 247 animateDimLayers(DimLayer.DimLayerUser dimLayerUser)248 private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) { 249 DimLayerState state = mState.get(dimLayerUser); 250 if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers," 251 + " dimLayerUser=" + dimLayerUser.toShortString() 252 + " state.animator=" + state.animator 253 + " state.continueDimming=" + state.continueDimming); 254 final int dimLayer; 255 final float dimAmount; 256 if (state.animator == null) { 257 dimLayer = state.dimLayer.getLayer(); 258 dimAmount = 0; 259 } else { 260 if (state.dimAbove) { 261 dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM; 262 dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW; 263 } else { 264 dimLayer = dimLayerUser.getLayerForDim(state.animator, LAYER_OFFSET_DIM, 265 state.animator.mAnimLayer - LAYER_OFFSET_DIM); 266 dimAmount = state.animator.mWin.mAttrs.dimAmount; 267 } 268 } 269 final float targetAlpha = state.dimLayer.getTargetAlpha(); 270 if (targetAlpha != dimAmount) { 271 if (state.animator == null) { 272 state.dimLayer.hide(DEFAULT_DIM_DURATION); 273 } else { 274 long duration = (state.animator.mAnimating && state.animator.mAnimation != null) 275 ? state.animator.mAnimation.computeDurationHint() 276 : DEFAULT_DIM_DURATION; 277 if (targetAlpha > dimAmount) { 278 duration = getDimLayerFadeDuration(duration); 279 } 280 state.dimLayer.show(dimLayer, dimAmount, duration); 281 282 // If we showed a dim layer, make sure to redo the layout because some things depend 283 // on whether a dim layer is showing or not. 284 if (targetAlpha == 0) { 285 mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT; 286 mDisplayContent.setLayoutNeeded(); 287 } 288 } 289 } else if (state.dimLayer.getLayer() != dimLayer) { 290 state.dimLayer.setLayer(dimLayer); 291 } 292 if (state.dimLayer.isAnimating()) { 293 if (!mDisplayContent.okToAnimate()) { 294 // Jump to the end of the animation. 295 state.dimLayer.show(); 296 } else { 297 return state.dimLayer.stepAnimation(); 298 } 299 } 300 return false; 301 } 302 isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator)303 boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) { 304 DimLayerState state = mState.get(dimLayerUser); 305 return state != null && state.animator == winAnimator && state.dimLayer.isDimming(); 306 } 307 getDimLayerFadeDuration(long duration)308 private long getDimLayerFadeDuration(long duration) { 309 TypedValue tv = new TypedValue(); 310 mDisplayContent.mService.mContext.getResources().getValue( 311 com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true); 312 if (tv.type == TypedValue.TYPE_FRACTION) { 313 duration = (long) tv.getFraction(duration, duration); 314 } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) { 315 duration = tv.data; 316 } 317 return duration; 318 } 319 close()320 void close() { 321 for (int i = mState.size() - 1; i >= 0; i--) { 322 DimLayerState state = mState.valueAt(i); 323 state.dimLayer.destroySurface(); 324 } 325 mState.clear(); 326 mSharedFullScreenDimLayer = null; 327 } 328 removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser)329 void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) { 330 DimLayerState state = mState.get(dimLayerUser); 331 if (state != null) { 332 // Destroy the surface, unless it's the shared fullscreen dim. 333 if (state.dimLayer != mSharedFullScreenDimLayer) { 334 state.dimLayer.destroySurface(); 335 } 336 mState.remove(dimLayerUser); 337 } 338 if (mState.isEmpty()) { 339 mSharedFullScreenDimLayer = null; 340 } 341 } 342 343 @VisibleForTesting hasDimLayerUser(DimLayer.DimLayerUser dimLayerUser)344 boolean hasDimLayerUser(DimLayer.DimLayerUser dimLayerUser) { 345 return mState.containsKey(dimLayerUser); 346 } 347 348 @VisibleForTesting hasSharedFullScreenDimLayer()349 boolean hasSharedFullScreenDimLayer() { 350 return mSharedFullScreenDimLayer != null; 351 } 352 applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator)353 void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) { 354 applyDim(dimLayerUser, animator, false /* aboveApp */); 355 } 356 applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator)357 void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) { 358 applyDim(dimLayerUser, animator, true /* aboveApp */); 359 } 360 applyDim( DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp)361 void applyDim( 362 DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) { 363 if (dimLayerUser == null) { 364 Slog.e(TAG, "Trying to apply dim layer for: " + this 365 + ", but no dim layer user found."); 366 return; 367 } 368 if (!getContinueDimming(dimLayerUser)) { 369 setContinueDimming(dimLayerUser); 370 if (!isDimming(dimLayerUser, animator)) { 371 if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming."); 372 startDimmingIfNeeded(dimLayerUser, animator, aboveApp); 373 } 374 } 375 } 376 377 private static class DimLayerState { 378 // The particular window requesting a dim layer. If null, hide dimLayer. 379 WindowStateAnimator animator; 380 // Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the 381 // end then stop any dimming. 382 boolean continueDimming; 383 DimLayer dimLayer; 384 boolean dimAbove; 385 } 386 dump(String prefix, PrintWriter pw)387 void dump(String prefix, PrintWriter pw) { 388 pw.println(prefix + "DimLayerController"); 389 final String doubleSpace = " "; 390 final String prefixPlusDoubleSpace = prefix + doubleSpace; 391 392 for (int i = 0, n = mState.size(); i < n; i++) { 393 pw.println(prefixPlusDoubleSpace + mState.keyAt(i).toShortString()); 394 DimLayerState state = mState.valueAt(i); 395 pw.println(prefixPlusDoubleSpace + doubleSpace + "dimLayer=" 396 + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" : state.dimLayer) 397 + ", animator=" + state.animator + ", continueDimming=" + state.continueDimming); 398 if (state.dimLayer != null) { 399 state.dimLayer.printTo(prefixPlusDoubleSpace + doubleSpace, pw); 400 } 401 } 402 } 403 } 404