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.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