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