blob: 6f9e45a6690966f1718b3a4b8aa2852aeff481e8 [file] [log] [blame]
package com.android.server.wm;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
import android.graphics.Rect;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.TypedValue;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.DimLayer.DimLayerUser;
import java.io.PrintWriter;
/**
* Centralizes the control of dim layers used for
* {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
* as well as other use cases (such as dimming above a dead window).
*/
class DimLayerController {
private static final String TAG_LOCAL = "DimLayerController";
private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
/** Amount of time in milliseconds to animate the dim surface from one value to another,
* when no window animation is driving it. */
private static final int DEFAULT_DIM_DURATION = 200;
/**
* The default amount of dim applied over a dead window
*/
private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
// Shared dim layer for fullscreen users. {@link DimLayerState#dimLayer} will point to this
// instead of creating a new object per fullscreen task on a display.
private DimLayer mSharedFullScreenDimLayer;
private ArrayMap<DimLayer.DimLayerUser, DimLayerState> mState = new ArrayMap<>();
private DisplayContent mDisplayContent;
private Rect mTmpBounds = new Rect();
DimLayerController(DisplayContent displayContent) {
mDisplayContent = displayContent;
}
/** Updates the dim layer bounds, recreating it if needed. */
void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
final DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
final boolean previousFullscreen = state.dimLayer != null
&& state.dimLayer == mSharedFullScreenDimLayer;
DimLayer newDimLayer;
final int displayId = mDisplayContent.getDisplayId();
if (dimLayerUser.dimFullscreen()) {
if (previousFullscreen && mSharedFullScreenDimLayer != null) {
// Update the bounds for fullscreen in case of rotation.
mSharedFullScreenDimLayer.setBoundsForFullscreen();
return;
}
// Use shared fullscreen dim layer
newDimLayer = mSharedFullScreenDimLayer;
if (newDimLayer == null) {
if (state.dimLayer != null) {
// Re-purpose the previous dim layer.
newDimLayer = state.dimLayer;
} else {
// Create new full screen dim layer.
newDimLayer = new DimLayer(mDisplayContent.mService, dimLayerUser, displayId,
getDimLayerTag(dimLayerUser));
}
dimLayerUser.getDimBounds(mTmpBounds);
newDimLayer.setBounds(mTmpBounds);
mSharedFullScreenDimLayer = newDimLayer;
} else if (state.dimLayer != null) {
state.dimLayer.destroySurface();
}
} else {
newDimLayer = (state.dimLayer == null || previousFullscreen)
? new DimLayer(mDisplayContent.mService, dimLayerUser, displayId,
getDimLayerTag(dimLayerUser))
: state.dimLayer;
dimLayerUser.getDimBounds(mTmpBounds);
newDimLayer.setBounds(mTmpBounds);
}
state.dimLayer = newDimLayer;
}
private static String getDimLayerTag(DimLayerUser dimLayerUser) {
return TAG_LOCAL + "/" + dimLayerUser.toShortString();
}
private DimLayerState getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser) {
if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser="
+ dimLayerUser.toShortString());
DimLayerState state = mState.get(dimLayerUser);
if (state == null) {
state = new DimLayerState();
mState.put(dimLayerUser, state);
}
return state;
}
private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
DimLayerState state = mState.get(dimLayerUser);
if (state == null) {
if (DEBUG_DIM_LAYER) Slog.w(TAG, "setContinueDimming, no state for: "
+ dimLayerUser.toShortString());
return;
}
state.continueDimming = true;
}
boolean isDimming() {
for (int i = mState.size() - 1; i >= 0; i--) {
DimLayerState state = mState.valueAt(i);
if (state.dimLayer != null && state.dimLayer.isDimming()) {
return true;
}
}
return false;
}
void resetDimming() {
for (int i = mState.size() - 1; i >= 0; i--) {
mState.valueAt(i).continueDimming = false;
}
}
private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
DimLayerState state = mState.get(dimLayerUser);
return state != null && state.continueDimming;
}
void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser,
WindowStateAnimator newWinAnimator, boolean aboveApp) {
// Only set dim params on the highest dimmed layer.
// Don't turn on for an unshown surface, or for any layer but the highest dimmed layer.
DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
state.dimAbove = aboveApp;
if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
+ " dimLayerUser=" + dimLayerUser.toShortString()
+ " newWinAnimator=" + newWinAnimator
+ " state.animator=" + state.animator);
if (newWinAnimator.getShown() && (state.animator == null
|| !state.animator.getShown()
|| state.animator.mAnimLayer <= newWinAnimator.mAnimLayer)) {
state.animator = newWinAnimator;
if (state.animator.mWin.mAppToken == null && !dimLayerUser.dimFullscreen()) {
// Dim should cover the entire screen for system windows.
mDisplayContent.getLogicalDisplayRect(mTmpBounds);
} else {
dimLayerUser.getDimBounds(mTmpBounds);
}
state.dimLayer.setBounds(mTmpBounds);
}
}
void stopDimmingIfNeeded() {
if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded, mState.size()=" + mState.size());
for (int i = mState.size() - 1; i >= 0; i--) {
DimLayer.DimLayerUser dimLayerUser = mState.keyAt(i);
stopDimmingIfNeeded(dimLayerUser);
}
}
private void stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser) {
// No need to check if state is null, we know the key has a value.
DimLayerState state = mState.get(dimLayerUser);
if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded,"
+ " dimLayerUser=" + dimLayerUser.toShortString()
+ " state.continueDimming=" + state.continueDimming
+ " state.dimLayer.isDimming=" + state.dimLayer.isDimming());
if (state.animator != null && state.animator.mWin.mWillReplaceWindow) {
return;
}
if (!state.continueDimming && state.dimLayer.isDimming()) {
state.animator = null;
dimLayerUser.getDimBounds(mTmpBounds);
state.dimLayer.setBounds(mTmpBounds);
}
}
boolean animateDimLayers() {
int fullScreen = -1;
int fullScreenAndDimming = -1;
int topFullScreenUserLayer = 0;
boolean result = false;
for (int i = mState.size() - 1; i >= 0; i--) {
final DimLayer.DimLayerUser user = mState.keyAt(i);
final DimLayerState state = mState.valueAt(i);
if (!user.isAttachedToDisplay()) {
// Leaked dim user that is no longer attached to the display. Go ahead and clean it
// clean-up and log what happened.
// TODO: This is a work around for b/34395537 as the dim user should have cleaned-up
// it self when it was detached from the display. Need to investigate how the dim
// user is leaking...
//Slog.wtfStack(TAG_WM, "Leaked dim user=" + user.toShortString()
// + " state=" + state);
Slog.w(TAG_WM, "Leaked dim user=" + user.toShortString() + " state=" + state);
removeDimLayerUser(user);
continue;
}
// We have to check that we are actually the shared fullscreen layer
// for this path. If we began as non fullscreen and became fullscreen
// (e.g. Docked stack closing), then we may not be the shared layer
// and we have to make sure we always animate the layer.
if (user.dimFullscreen() && state.dimLayer == mSharedFullScreenDimLayer) {
fullScreen = i;
if (!state.continueDimming) {
continue;
}
// When choosing which user to assign the shared fullscreen layer to
// we need to look at Z-order.
if (topFullScreenUserLayer == 0 ||
(state.animator != null && state.animator.mAnimLayer > topFullScreenUserLayer)) {
fullScreenAndDimming = i;
if (state.animator != null) {
topFullScreenUserLayer = state.animator.mAnimLayer;
}
}
} else {
// We always want to animate the non fullscreen windows, they don't share their
// dim layers.
result |= animateDimLayers(user);
}
}
// For the shared, full screen dim layer, we prefer the animation that is causing it to
// appear.
if (fullScreenAndDimming != -1) {
result |= animateDimLayers(mState.keyAt(fullScreenAndDimming));
} else if (fullScreen != -1) {
// If there is no animation for the full screen dim layer to appear, we can use any of
// the animators that will cause it to disappear.
result |= animateDimLayers(mState.keyAt(fullScreen));
}
return result;
}
private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
DimLayerState state = mState.get(dimLayerUser);
if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers,"
+ " dimLayerUser=" + dimLayerUser.toShortString()
+ " state.animator=" + state.animator
+ " state.continueDimming=" + state.continueDimming);
final int dimLayer;
final float dimAmount;
if (state.animator == null) {
dimLayer = state.dimLayer.getLayer();
dimAmount = 0;
} else {
if (state.dimAbove) {
dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM;
dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW;
} else {
dimLayer = dimLayerUser.getLayerForDim(state.animator, LAYER_OFFSET_DIM,
state.animator.mAnimLayer - LAYER_OFFSET_DIM);
dimAmount = state.animator.mWin.mAttrs.dimAmount;
}
}
final float targetAlpha = state.dimLayer.getTargetAlpha();
if (targetAlpha != dimAmount) {
if (state.animator == null) {
state.dimLayer.hide(DEFAULT_DIM_DURATION);
} else {
long duration = (state.animator.mAnimating && state.animator.mAnimation != null)
? state.animator.mAnimation.computeDurationHint()
: DEFAULT_DIM_DURATION;
if (targetAlpha > dimAmount) {
duration = getDimLayerFadeDuration(duration);
}
state.dimLayer.show(dimLayer, dimAmount, duration);
// If we showed a dim layer, make sure to redo the layout because some things depend
// on whether a dim layer is showing or not.
if (targetAlpha == 0) {
mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
mDisplayContent.setLayoutNeeded();
}
}
} else if (state.dimLayer.getLayer() != dimLayer) {
state.dimLayer.setLayer(dimLayer);
}
if (state.dimLayer.isAnimating()) {
if (!mDisplayContent.okToAnimate()) {
// Jump to the end of the animation.
state.dimLayer.show();
} else {
return state.dimLayer.stepAnimation();
}
}
return false;
}
boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
DimLayerState state = mState.get(dimLayerUser);
return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
}
private long getDimLayerFadeDuration(long duration) {
TypedValue tv = new TypedValue();
mDisplayContent.mService.mContext.getResources().getValue(
com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
if (tv.type == TypedValue.TYPE_FRACTION) {
duration = (long) tv.getFraction(duration, duration);
} else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) {
duration = tv.data;
}
return duration;
}
void close() {
for (int i = mState.size() - 1; i >= 0; i--) {
DimLayerState state = mState.valueAt(i);
state.dimLayer.destroySurface();
}
mState.clear();
mSharedFullScreenDimLayer = null;
}
void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
DimLayerState state = mState.get(dimLayerUser);
if (state != null) {
// Destroy the surface, unless it's the shared fullscreen dim.
if (state.dimLayer != mSharedFullScreenDimLayer) {
state.dimLayer.destroySurface();
}
mState.remove(dimLayerUser);
}
if (mState.isEmpty()) {
mSharedFullScreenDimLayer = null;
}
}
@VisibleForTesting
boolean hasDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
return mState.containsKey(dimLayerUser);
}
@VisibleForTesting
boolean hasSharedFullScreenDimLayer() {
return mSharedFullScreenDimLayer != null;
}
void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
applyDim(dimLayerUser, animator, false /* aboveApp */);
}
void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
applyDim(dimLayerUser, animator, true /* aboveApp */);
}
void applyDim(
DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) {
if (dimLayerUser == null) {
Slog.e(TAG, "Trying to apply dim layer for: " + this
+ ", but no dim layer user found.");
return;
}
if (!getContinueDimming(dimLayerUser)) {
setContinueDimming(dimLayerUser);
if (!isDimming(dimLayerUser, animator)) {
if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
startDimmingIfNeeded(dimLayerUser, animator, aboveApp);
}
}
}
private static class DimLayerState {
// The particular window requesting a dim layer. If null, hide dimLayer.
WindowStateAnimator animator;
// Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the
// end then stop any dimming.
boolean continueDimming;
DimLayer dimLayer;
boolean dimAbove;
}
void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "DimLayerController");
final String doubleSpace = " ";
final String prefixPlusDoubleSpace = prefix + doubleSpace;
for (int i = 0, n = mState.size(); i < n; i++) {
pw.println(prefixPlusDoubleSpace + mState.keyAt(i).toShortString());
DimLayerState state = mState.valueAt(i);
pw.println(prefixPlusDoubleSpace + doubleSpace + "dimLayer="
+ (state.dimLayer == mSharedFullScreenDimLayer ? "shared" : state.dimLayer)
+ ", animator=" + state.animator + ", continueDimming=" + state.continueDimming);
if (state.dimLayer != null) {
state.dimLayer.printTo(prefixPlusDoubleSpace + doubleSpace, pw);
}
}
}
}