Home | History | Annotate | Download | only in wm
      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 
     47     DimLayerController(DisplayContent displayContent) {
     48         mDisplayContent = displayContent;
     49     }
     50 
     51     /** Updates the dim layer bounds, recreating it if needed. */
     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 
     92     private static String getDimLayerTag(DimLayerUser dimLayerUser) {
     93         return TAG_LOCAL + "/" + dimLayerUser.toShortString();
     94     }
     95 
     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 
    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 
    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 
    127     void resetDimming() {
    128         for (int i = mState.size() - 1; i >= 0; i--) {
    129             mState.valueAt(i).continueDimming = false;
    130         }
    131     }
    132 
    133     private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
    134         DimLayerState state = mState.get(dimLayerUser);
    135         return state != null && state.continueDimming;
    136     }
    137 
    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 
    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 
    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 
    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 
    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 = state.animator.mAnimLayer - LAYER_OFFSET_DIM;
    265                 dimAmount = state.animator.mWin.mAttrs.dimAmount;
    266             }
    267         }
    268         final float targetAlpha = state.dimLayer.getTargetAlpha();
    269         if (targetAlpha != dimAmount) {
    270             if (state.animator == null) {
    271                 state.dimLayer.hide(DEFAULT_DIM_DURATION);
    272             } else {
    273                 long duration = (state.animator.mAnimating && state.animator.mAnimation != null)
    274                         ? state.animator.mAnimation.computeDurationHint()
    275                         : DEFAULT_DIM_DURATION;
    276                 if (targetAlpha > dimAmount) {
    277                     duration = getDimLayerFadeDuration(duration);
    278                 }
    279                 state.dimLayer.show(dimLayer, dimAmount, duration);
    280 
    281                 // If we showed a dim layer, make sure to redo the layout because some things depend
    282                 // on whether a dim layer is showing or not.
    283                 if (targetAlpha == 0) {
    284                     mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
    285                     mDisplayContent.setLayoutNeeded();
    286                 }
    287             }
    288         } else if (state.dimLayer.getLayer() != dimLayer) {
    289             state.dimLayer.setLayer(dimLayer);
    290         }
    291         if (state.dimLayer.isAnimating()) {
    292             if (!mDisplayContent.mService.okToDisplay()) {
    293                 // Jump to the end of the animation.
    294                 state.dimLayer.show();
    295             } else {
    296                 return state.dimLayer.stepAnimation();
    297             }
    298         }
    299         return false;
    300     }
    301 
    302     boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
    303         DimLayerState state = mState.get(dimLayerUser);
    304         return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
    305     }
    306 
    307     private long getDimLayerFadeDuration(long duration) {
    308         TypedValue tv = new TypedValue();
    309         mDisplayContent.mService.mContext.getResources().getValue(
    310                 com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
    311         if (tv.type == TypedValue.TYPE_FRACTION) {
    312             duration = (long) tv.getFraction(duration, duration);
    313         } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) {
    314             duration = tv.data;
    315         }
    316         return duration;
    317     }
    318 
    319     void close() {
    320         for (int i = mState.size() - 1; i >= 0; i--) {
    321             DimLayerState state = mState.valueAt(i);
    322             state.dimLayer.destroySurface();
    323         }
    324         mState.clear();
    325         mSharedFullScreenDimLayer = null;
    326     }
    327 
    328     void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
    329         DimLayerState state = mState.get(dimLayerUser);
    330         if (state != null) {
    331             // Destroy the surface, unless it's the shared fullscreen dim.
    332             if (state.dimLayer != mSharedFullScreenDimLayer) {
    333                 state.dimLayer.destroySurface();
    334             }
    335             mState.remove(dimLayerUser);
    336         }
    337         if (mState.isEmpty()) {
    338             mSharedFullScreenDimLayer = null;
    339         }
    340     }
    341 
    342     @VisibleForTesting
    343     boolean hasDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
    344         return mState.containsKey(dimLayerUser);
    345     }
    346 
    347     @VisibleForTesting
    348     boolean hasSharedFullScreenDimLayer() {
    349         return mSharedFullScreenDimLayer != null;
    350     }
    351 
    352     void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
    353         applyDim(dimLayerUser, animator, false /* aboveApp */);
    354     }
    355 
    356     void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
    357         applyDim(dimLayerUser, animator, true /* aboveApp */);
    358     }
    359 
    360     void applyDim(
    361             DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) {
    362         if (dimLayerUser == null) {
    363             Slog.e(TAG, "Trying to apply dim layer for: " + this
    364                     + ", but no dim layer user found.");
    365             return;
    366         }
    367         if (!getContinueDimming(dimLayerUser)) {
    368             setContinueDimming(dimLayerUser);
    369             if (!isDimming(dimLayerUser, animator)) {
    370                 if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
    371                 startDimmingIfNeeded(dimLayerUser, animator, aboveApp);
    372             }
    373         }
    374     }
    375 
    376     private static class DimLayerState {
    377         // The particular window requesting a dim layer. If null, hide dimLayer.
    378         WindowStateAnimator animator;
    379         // Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the
    380         // end then stop any dimming.
    381         boolean continueDimming;
    382         DimLayer dimLayer;
    383         boolean dimAbove;
    384     }
    385 
    386     void dump(String prefix, PrintWriter pw) {
    387         pw.println(prefix + "DimLayerController");
    388         final String doubleSpace = "  ";
    389         final String prefixPlusDoubleSpace = prefix + doubleSpace;
    390 
    391         for (int i = 0, n = mState.size(); i < n; i++) {
    392             pw.println(prefixPlusDoubleSpace + mState.keyAt(i).toShortString());
    393             DimLayerState state = mState.valueAt(i);
    394             pw.println(prefixPlusDoubleSpace + doubleSpace + "dimLayer="
    395                     + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" : state.dimLayer)
    396                     + ", animator=" + state.animator + ", continueDimming=" + state.continueDimming);
    397             if (state.dimLayer != null) {
    398                 state.dimLayer.printTo(prefixPlusDoubleSpace + doubleSpace, pw);
    399             }
    400         }
    401     }
    402 }
    403