Home | History | Annotate | Download | only in wm
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.wm;
     18 
     19 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
     20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
     21 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
     22 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
     23 import static android.view.Surface.ROTATION_270;
     24 import static android.view.Surface.ROTATION_90;
     25 import static android.view.WindowManager.DOCKED_BOTTOM;
     26 import static android.view.WindowManager.DOCKED_INVALID;
     27 import static android.view.WindowManager.DOCKED_LEFT;
     28 import static android.view.WindowManager.DOCKED_RIGHT;
     29 import static android.view.WindowManager.DOCKED_TOP;
     30 import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION;
     31 import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR;
     32 import static android.view.WindowManager.TRANSIT_NONE;
     33 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
     34 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
     35 import static com.android.server.wm.WindowManagerService.H.NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED;
     36 import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
     37 import static com.android.server.wm.DockedStackDividerControllerProto.MINIMIZED_DOCK;
     38 
     39 import android.content.Context;
     40 import android.content.res.Configuration;
     41 import android.graphics.Rect;
     42 import android.os.RemoteCallbackList;
     43 import android.os.RemoteException;
     44 import android.util.ArraySet;
     45 import android.util.Slog;
     46 import android.util.proto.ProtoOutputStream;
     47 import android.view.DisplayCutout;
     48 import android.view.DisplayInfo;
     49 import android.view.IDockedStackListener;
     50 import android.view.animation.AnimationUtils;
     51 import android.view.animation.Interpolator;
     52 import android.view.animation.PathInterpolator;
     53 import android.view.inputmethod.InputMethodManagerInternal;
     54 
     55 import com.android.internal.policy.DividerSnapAlgorithm;
     56 import com.android.internal.policy.DockedDividerUtils;
     57 import com.android.server.LocalServices;
     58 import com.android.server.wm.WindowManagerService.H;
     59 
     60 import java.io.PrintWriter;
     61 
     62 /**
     63  * Keeps information about the docked stack divider.
     64  */
     65 public class DockedStackDividerController {
     66 
     67     private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM;
     68 
     69     /**
     70      * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip
     71      * revealing surface at the earliest.
     72      */
     73     private static final float CLIP_REVEAL_MEET_EARLIEST = 0.6f;
     74 
     75     /**
     76      * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip
     77      * revealing surface at the latest.
     78      */
     79     private static final float CLIP_REVEAL_MEET_LAST = 1f;
     80 
     81     /**
     82      * If the app translates at least CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance, we start
     83      * meet somewhere between {@link #CLIP_REVEAL_MEET_LAST} and {@link #CLIP_REVEAL_MEET_EARLIEST}.
     84      */
     85     private static final float CLIP_REVEAL_MEET_FRACTION_MIN = 0.4f;
     86 
     87     /**
     88      * If the app translates equals or more than CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance,
     89      * we meet at {@link #CLIP_REVEAL_MEET_EARLIEST}.
     90      */
     91     private static final float CLIP_REVEAL_MEET_FRACTION_MAX = 0.8f;
     92 
     93     private static final Interpolator IME_ADJUST_ENTRY_INTERPOLATOR =
     94             new PathInterpolator(0.2f, 0f, 0.1f, 1f);
     95 
     96     private static final long IME_ADJUST_ANIM_DURATION = 280;
     97 
     98     private static final long IME_ADJUST_DRAWN_TIMEOUT = 200;
     99 
    100     private static final int DIVIDER_WIDTH_INACTIVE_DP = 4;
    101 
    102     private final WindowManagerService mService;
    103     private final DisplayContent mDisplayContent;
    104     private int mDividerWindowWidth;
    105     private int mDividerWindowWidthInactive;
    106     private int mDividerInsets;
    107     private int mTaskHeightInMinimizedMode;
    108     private boolean mResizing;
    109     private WindowState mWindow;
    110     private final Rect mTmpRect = new Rect();
    111     private final Rect mTmpRect2 = new Rect();
    112     private final Rect mTmpRect3 = new Rect();
    113     private final Rect mLastRect = new Rect();
    114     private boolean mLastVisibility = false;
    115     private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners
    116             = new RemoteCallbackList<>();
    117 
    118     private boolean mMinimizedDock;
    119     private int mOriginalDockedSide = DOCKED_INVALID;
    120     private boolean mAnimatingForMinimizedDockedStack;
    121     private boolean mAnimationStarted;
    122     private long mAnimationStartTime;
    123     private float mAnimationStart;
    124     private float mAnimationTarget;
    125     private long mAnimationDuration;
    126     private boolean mAnimationStartDelayed;
    127     private final Interpolator mMinimizedDockInterpolator;
    128     private float mMaximizeMeetFraction;
    129     private final Rect mTouchRegion = new Rect();
    130     private boolean mAnimatingForIme;
    131     private boolean mAdjustedForIme;
    132     private int mImeHeight;
    133     private WindowState mDelayedImeWin;
    134     private boolean mAdjustedForDivider;
    135     private float mDividerAnimationStart;
    136     private float mDividerAnimationTarget;
    137     float mLastAnimationProgress;
    138     float mLastDividerProgress;
    139     private final DividerSnapAlgorithm[] mSnapAlgorithmForRotation = new DividerSnapAlgorithm[4];
    140     private boolean mImeHideRequested;
    141     private final Rect mLastDimLayerRect = new Rect();
    142     private float mLastDimLayerAlpha;
    143     private TaskStack mDimmedStack;
    144 
    145     DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
    146         mService = service;
    147         mDisplayContent = displayContent;
    148         final Context context = service.mContext;
    149         mMinimizedDockInterpolator = AnimationUtils.loadInterpolator(
    150                 context, android.R.interpolator.fast_out_slow_in);
    151         loadDimens();
    152     }
    153 
    154     int getSmallestWidthDpForBounds(Rect bounds) {
    155         final DisplayInfo di = mDisplayContent.getDisplayInfo();
    156 
    157         final int baseDisplayWidth = mDisplayContent.mBaseDisplayWidth;
    158         final int baseDisplayHeight = mDisplayContent.mBaseDisplayHeight;
    159         int minWidth = Integer.MAX_VALUE;
    160 
    161         // Go through all screen orientations and find the orientation in which the task has the
    162         // smallest width.
    163         for (int rotation = 0; rotation < 4; rotation++) {
    164             mTmpRect.set(bounds);
    165             mDisplayContent.rotateBounds(di.rotation, rotation, mTmpRect);
    166             final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
    167             mTmpRect2.set(0, 0,
    168                     rotated ? baseDisplayHeight : baseDisplayWidth,
    169                     rotated ? baseDisplayWidth : baseDisplayHeight);
    170             final int orientation = mTmpRect2.width() <= mTmpRect2.height()
    171                     ? ORIENTATION_PORTRAIT
    172                     : ORIENTATION_LANDSCAPE;
    173             final int dockSide = getDockSide(mTmpRect, mTmpRect2, orientation);
    174             final int position = DockedDividerUtils.calculatePositionForBounds(mTmpRect, dockSide,
    175                     getContentWidth());
    176 
    177             final DisplayCutout displayCutout = mDisplayContent.calculateDisplayCutoutForRotation(
    178                     rotation).getDisplayCutout();
    179 
    180             // Since we only care about feasible states, snap to the closest snap target, like it
    181             // would happen when actually rotating the screen.
    182             final int snappedPosition = mSnapAlgorithmForRotation[rotation]
    183                     .calculateNonDismissingSnapTarget(position).position;
    184             DockedDividerUtils.calculateBoundsForPosition(snappedPosition, dockSide, mTmpRect,
    185                     mTmpRect2.width(), mTmpRect2.height(), getContentWidth());
    186             mService.mPolicy.getStableInsetsLw(rotation, mTmpRect2.width(), mTmpRect2.height(),
    187                     displayCutout, mTmpRect3);
    188             mService.intersectDisplayInsetBounds(mTmpRect2, mTmpRect3, mTmpRect);
    189             minWidth = Math.min(mTmpRect.width(), minWidth);
    190         }
    191         return (int) (minWidth / mDisplayContent.getDisplayMetrics().density);
    192     }
    193 
    194     /**
    195      * Get the current docked side. Determined by its location of {@param bounds} within
    196      * {@param displayRect} but if both are the same, it will try to dock to each side and determine
    197      * if allowed in its respected {@param orientation}.
    198      *
    199      * @param bounds bounds of the docked task to get which side is docked
    200      * @param displayRect bounds of the display that contains the docked task
    201      * @param orientation the origination of device
    202      * @return current docked side
    203      */
    204     int getDockSide(Rect bounds, Rect displayRect, int orientation) {
    205         if (orientation == Configuration.ORIENTATION_PORTRAIT) {
    206             // Portrait mode, docked either at the top or the bottom.
    207             final int diff = (displayRect.bottom - bounds.bottom) - (bounds.top - displayRect.top);
    208             if (diff > 0) {
    209                 return DOCKED_TOP;
    210             } else if (diff < 0) {
    211                 return DOCKED_BOTTOM;
    212             }
    213             return canPrimaryStackDockTo(DOCKED_TOP) ? DOCKED_TOP : DOCKED_BOTTOM;
    214         } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
    215             // Landscape mode, docked either on the left or on the right.
    216             final int diff = (displayRect.right - bounds.right) - (bounds.left - displayRect.left);
    217             if (diff > 0) {
    218                 return DOCKED_LEFT;
    219             } else if (diff < 0) {
    220                 return DOCKED_RIGHT;
    221             }
    222             return canPrimaryStackDockTo(DOCKED_LEFT) ? DOCKED_LEFT : DOCKED_RIGHT;
    223         }
    224         return DOCKED_INVALID;
    225     }
    226 
    227     void getHomeStackBoundsInDockedMode(Rect outBounds) {
    228         final DisplayInfo di = mDisplayContent.getDisplayInfo();
    229         mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
    230                 di.displayCutout, mTmpRect);
    231         int dividerSize = mDividerWindowWidth - 2 * mDividerInsets;
    232         Configuration configuration = mDisplayContent.getConfiguration();
    233         // The offset in the left (landscape)/top (portrait) is calculated with the minimized
    234         // offset value with the divider size and any system insets in that direction.
    235         if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
    236             outBounds.set(0, mTaskHeightInMinimizedMode + dividerSize + mTmpRect.top,
    237                     di.logicalWidth, di.logicalHeight);
    238         } else {
    239             // In landscape also inset the left/right side with the statusbar height to match the
    240             // minimized size height in portrait mode.
    241             final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
    242             final int primaryTaskWidth = mTaskHeightInMinimizedMode + dividerSize + mTmpRect.top;
    243             int left = mTmpRect.left;
    244             int right = di.logicalWidth - mTmpRect.right;
    245             if (stack != null) {
    246                 if (stack.getDockSide() == DOCKED_LEFT) {
    247                     left += primaryTaskWidth;
    248                 } else if (stack.getDockSide() == DOCKED_RIGHT) {
    249                     right -= primaryTaskWidth;
    250                 }
    251             }
    252             outBounds.set(left, 0, right, di.logicalHeight);
    253         }
    254     }
    255 
    256     boolean isHomeStackResizable() {
    257         final TaskStack homeStack = mDisplayContent.getHomeStack();
    258         if (homeStack == null) {
    259             return false;
    260         }
    261         final Task homeTask = homeStack.findHomeTask();
    262         return homeTask != null && homeTask.isResizeable();
    263     }
    264 
    265     private void initSnapAlgorithmForRotations() {
    266         final Configuration baseConfig = mDisplayContent.getConfiguration();
    267 
    268         // Initialize the snap algorithms for all 4 screen orientations.
    269         final Configuration config = new Configuration();
    270         for (int rotation = 0; rotation < 4; rotation++) {
    271             final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
    272             final int dw = rotated
    273                     ? mDisplayContent.mBaseDisplayHeight
    274                     : mDisplayContent.mBaseDisplayWidth;
    275             final int dh = rotated
    276                     ? mDisplayContent.mBaseDisplayWidth
    277                     : mDisplayContent.mBaseDisplayHeight;
    278             final DisplayCutout displayCutout =
    279                     mDisplayContent.calculateDisplayCutoutForRotation(rotation).getDisplayCutout();
    280             mService.mPolicy.getStableInsetsLw(rotation, dw, dh, displayCutout, mTmpRect);
    281             config.unset();
    282             config.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
    283 
    284             final int displayId = mDisplayContent.getDisplayId();
    285             final int appWidth = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, rotation,
    286                 baseConfig.uiMode, displayId, displayCutout);
    287             final int appHeight = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, rotation,
    288                 baseConfig.uiMode, displayId, displayCutout);
    289             mService.mPolicy.getNonDecorInsetsLw(rotation, dw, dh, displayCutout, mTmpRect);
    290             final int leftInset = mTmpRect.left;
    291             final int topInset = mTmpRect.top;
    292 
    293             config.windowConfiguration.setAppBounds(leftInset /*left*/, topInset /*top*/,
    294                     leftInset + appWidth /*right*/, topInset + appHeight /*bottom*/);
    295 
    296             final float density = mDisplayContent.getDisplayMetrics().density;
    297             config.screenWidthDp = (int) (mService.mPolicy.getConfigDisplayWidth(dw, dh,
    298                     rotation, baseConfig.uiMode, displayId, displayCutout) / density);
    299             config.screenHeightDp = (int) (mService.mPolicy.getConfigDisplayHeight(dw, dh,
    300                     rotation, baseConfig.uiMode, displayId, displayCutout) / density);
    301             final Context rotationContext = mService.mContext.createConfigurationContext(config);
    302             mSnapAlgorithmForRotation[rotation] = new DividerSnapAlgorithm(
    303                     rotationContext.getResources(), dw, dh, getContentWidth(),
    304                     config.orientation == ORIENTATION_PORTRAIT, mTmpRect);
    305         }
    306     }
    307 
    308     private void loadDimens() {
    309         final Context context = mService.mContext;
    310         mDividerWindowWidth = context.getResources().getDimensionPixelSize(
    311                 com.android.internal.R.dimen.docked_stack_divider_thickness);
    312         mDividerInsets = context.getResources().getDimensionPixelSize(
    313                 com.android.internal.R.dimen.docked_stack_divider_insets);
    314         mDividerWindowWidthInactive = WindowManagerService.dipToPixel(
    315                 DIVIDER_WIDTH_INACTIVE_DP, mDisplayContent.getDisplayMetrics());
    316         mTaskHeightInMinimizedMode = context.getResources().getDimensionPixelSize(
    317                 com.android.internal.R.dimen.task_height_of_minimized_mode);
    318         initSnapAlgorithmForRotations();
    319     }
    320 
    321     void onConfigurationChanged() {
    322         loadDimens();
    323     }
    324 
    325     boolean isResizing() {
    326         return mResizing;
    327     }
    328 
    329     int getContentWidth() {
    330         return mDividerWindowWidth - 2 * mDividerInsets;
    331     }
    332 
    333     int getContentInsets() {
    334         return mDividerInsets;
    335     }
    336 
    337     int getContentWidthInactive() {
    338         return mDividerWindowWidthInactive;
    339     }
    340 
    341     void setResizing(boolean resizing) {
    342         if (mResizing != resizing) {
    343             mResizing = resizing;
    344             resetDragResizingChangeReported();
    345         }
    346     }
    347 
    348     void setTouchRegion(Rect touchRegion) {
    349         mTouchRegion.set(touchRegion);
    350     }
    351 
    352     void getTouchRegion(Rect outRegion) {
    353         outRegion.set(mTouchRegion);
    354         outRegion.offset(mWindow.getFrameLw().left, mWindow.getFrameLw().top);
    355     }
    356 
    357     private void resetDragResizingChangeReported() {
    358         mDisplayContent.forAllWindows(WindowState::resetDragResizingChangeReported,
    359                 true /* traverseTopToBottom */ );
    360     }
    361 
    362     void setWindow(WindowState window) {
    363         mWindow = window;
    364         reevaluateVisibility(false);
    365     }
    366 
    367     void reevaluateVisibility(boolean force) {
    368         if (mWindow == null) {
    369             return;
    370         }
    371         TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
    372 
    373         // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide
    374         final boolean visible = stack != null;
    375         if (mLastVisibility == visible && !force) {
    376             return;
    377         }
    378         mLastVisibility = visible;
    379         notifyDockedDividerVisibilityChanged(visible);
    380         if (!visible) {
    381             setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f);
    382         }
    383     }
    384 
    385     private boolean wasVisible() {
    386         return mLastVisibility;
    387     }
    388 
    389     void setAdjustedForIme(
    390             boolean adjustedForIme, boolean adjustedForDivider,
    391             boolean animate, WindowState imeWin, int imeHeight) {
    392         if (mAdjustedForIme != adjustedForIme || (adjustedForIme && mImeHeight != imeHeight)
    393                 || mAdjustedForDivider != adjustedForDivider) {
    394             if (animate && !mAnimatingForMinimizedDockedStack) {
    395                 startImeAdjustAnimation(adjustedForIme, adjustedForDivider, imeWin);
    396             } else {
    397                 // Animation might be delayed, so only notify if we don't run an animation.
    398                 notifyAdjustedForImeChanged(adjustedForIme || adjustedForDivider, 0 /* duration */);
    399             }
    400             mAdjustedForIme = adjustedForIme;
    401             mImeHeight = imeHeight;
    402             mAdjustedForDivider = adjustedForDivider;
    403         }
    404     }
    405 
    406     int getImeHeightAdjustedFor() {
    407         return mImeHeight;
    408     }
    409 
    410     void positionDockedStackedDivider(Rect frame) {
    411         TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
    412         if (stack == null) {
    413             // Unfortunately we might end up with still having a divider, even though the underlying
    414             // stack was already removed. This is because we are on AM thread and the removal of the
    415             // divider was deferred to WM thread and hasn't happened yet. In that case let's just
    416             // keep putting it in the same place it was before the stack was removed to have
    417             // continuity and prevent it from jumping to the center. It will get hidden soon.
    418             frame.set(mLastRect);
    419             return;
    420         } else {
    421             stack.getDimBounds(mTmpRect);
    422         }
    423         int side = stack.getDockSide();
    424         switch (side) {
    425             case DOCKED_LEFT:
    426                 frame.set(mTmpRect.right - mDividerInsets, frame.top,
    427                         mTmpRect.right + frame.width() - mDividerInsets, frame.bottom);
    428                 break;
    429             case DOCKED_TOP:
    430                 frame.set(frame.left, mTmpRect.bottom - mDividerInsets,
    431                         mTmpRect.right, mTmpRect.bottom + frame.height() - mDividerInsets);
    432                 break;
    433             case DOCKED_RIGHT:
    434                 frame.set(mTmpRect.left - frame.width() + mDividerInsets, frame.top,
    435                         mTmpRect.left + mDividerInsets, frame.bottom);
    436                 break;
    437             case DOCKED_BOTTOM:
    438                 frame.set(frame.left, mTmpRect.top - frame.height() + mDividerInsets,
    439                         frame.right, mTmpRect.top + mDividerInsets);
    440                 break;
    441         }
    442         mLastRect.set(frame);
    443     }
    444 
    445     private void notifyDockedDividerVisibilityChanged(boolean visible) {
    446         final int size = mDockedStackListeners.beginBroadcast();
    447         for (int i = 0; i < size; ++i) {
    448             final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
    449             try {
    450                 listener.onDividerVisibilityChanged(visible);
    451             } catch (RemoteException e) {
    452                 Slog.e(TAG_WM, "Error delivering divider visibility changed event.", e);
    453             }
    454         }
    455         mDockedStackListeners.finishBroadcast();
    456     }
    457 
    458     /**
    459      * Checks if the primary stack is allowed to dock to a specific side based on its original dock
    460      * side.
    461      *
    462      * @param dockSide the side to see if it is valid
    463      * @return true if the side provided is valid
    464      */
    465     boolean canPrimaryStackDockTo(int dockSide) {
    466         final DisplayInfo di = mDisplayContent.getDisplayInfo();
    467         return mService.mPolicy.isDockSideAllowed(dockSide, mOriginalDockedSide, di.logicalWidth,
    468                 di.logicalHeight, di.rotation);
    469     }
    470 
    471     void notifyDockedStackExistsChanged(boolean exists) {
    472         // TODO(multi-display): Perform all actions only for current display.
    473         final int size = mDockedStackListeners.beginBroadcast();
    474         for (int i = 0; i < size; ++i) {
    475             final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
    476             try {
    477                 listener.onDockedStackExistsChanged(exists);
    478             } catch (RemoteException e) {
    479                 Slog.e(TAG_WM, "Error delivering docked stack exists changed event.", e);
    480             }
    481         }
    482         mDockedStackListeners.finishBroadcast();
    483         if (exists) {
    484             InputMethodManagerInternal inputMethodManagerInternal =
    485                     LocalServices.getService(InputMethodManagerInternal.class);
    486             if (inputMethodManagerInternal != null) {
    487 
    488                 // Hide the current IME to avoid problems with animations from IME adjustment when
    489                 // attaching the docked stack.
    490                 inputMethodManagerInternal.hideCurrentInputMethod();
    491                 mImeHideRequested = true;
    492             }
    493 
    494             // If a primary stack was just created, it will not have access to display content at
    495             // this point so pass it from here to get a valid dock side.
    496             final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
    497             mOriginalDockedSide = stack.getDockSideForDisplay(mDisplayContent);
    498             return;
    499         }
    500         mOriginalDockedSide = DOCKED_INVALID;
    501         setMinimizedDockedStack(false /* minimizedDock */, false /* animate */);
    502 
    503         if (mDimmedStack != null) {
    504             mDimmedStack.stopDimming();
    505             mDimmedStack = null;
    506         }
    507     }
    508 
    509     /**
    510      * Resets the state that IME hide has been requested. See {@link #isImeHideRequested}.
    511      */
    512     void resetImeHideRequested() {
    513         mImeHideRequested = false;
    514     }
    515 
    516     /**
    517      * The docked stack divider controller makes sure the IME gets hidden when attaching the docked
    518      * stack, to avoid animation problems. This flag indicates whether the request to hide the IME
    519      * has been sent in an asynchronous manner, and the IME should be treated as hidden already.
    520      *
    521      * @return whether IME hide request has been sent
    522      */
    523     boolean isImeHideRequested() {
    524         return mImeHideRequested;
    525     }
    526 
    527     private void notifyDockedStackMinimizedChanged(boolean minimizedDock, boolean animate,
    528             boolean isHomeStackResizable) {
    529         long animDuration = 0;
    530         if (animate) {
    531             final TaskStack stack =
    532                     mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
    533             final long transitionDuration = isAnimationMaximizing()
    534                     ? mService.mAppTransition.getLastClipRevealTransitionDuration()
    535                     : DEFAULT_APP_TRANSITION_DURATION;
    536             mAnimationDuration = (long)
    537                     (transitionDuration * mService.getTransitionAnimationScaleLocked());
    538             mMaximizeMeetFraction = getClipRevealMeetFraction(stack);
    539             animDuration = (long) (mAnimationDuration * mMaximizeMeetFraction);
    540         }
    541         mService.mH.removeMessages(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED);
    542         mService.mH.obtainMessage(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED,
    543                 minimizedDock ? 1 : 0, 0).sendToTarget();
    544         final int size = mDockedStackListeners.beginBroadcast();
    545         for (int i = 0; i < size; ++i) {
    546             final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
    547             try {
    548                 listener.onDockedStackMinimizedChanged(minimizedDock, animDuration,
    549                         isHomeStackResizable);
    550             } catch (RemoteException e) {
    551                 Slog.e(TAG_WM, "Error delivering minimized dock changed event.", e);
    552             }
    553         }
    554         mDockedStackListeners.finishBroadcast();
    555     }
    556 
    557     void notifyDockSideChanged(int newDockSide) {
    558         final int size = mDockedStackListeners.beginBroadcast();
    559         for (int i = 0; i < size; ++i) {
    560             final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
    561             try {
    562                 listener.onDockSideChanged(newDockSide);
    563             } catch (RemoteException e) {
    564                 Slog.e(TAG_WM, "Error delivering dock side changed event.", e);
    565             }
    566         }
    567         mDockedStackListeners.finishBroadcast();
    568     }
    569 
    570     private void notifyAdjustedForImeChanged(boolean adjustedForIme, long animDuration) {
    571         final int size = mDockedStackListeners.beginBroadcast();
    572         for (int i = 0; i < size; ++i) {
    573             final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
    574             try {
    575                 listener.onAdjustedForImeChanged(adjustedForIme, animDuration);
    576             } catch (RemoteException e) {
    577                 Slog.e(TAG_WM, "Error delivering adjusted for ime changed event.", e);
    578             }
    579         }
    580         mDockedStackListeners.finishBroadcast();
    581     }
    582 
    583     void registerDockedStackListener(IDockedStackListener listener) {
    584         mDockedStackListeners.register(listener);
    585         notifyDockedDividerVisibilityChanged(wasVisible());
    586         notifyDockedStackExistsChanged(
    587                 mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null);
    588         notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */,
    589                 isHomeStackResizable());
    590         notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
    591 
    592     }
    593 
    594     /**
    595      * Shows a dim layer with {@param alpha} if {@param visible} is true and
    596      * {@param targetWindowingMode} isn't
    597      * {@link android.app.WindowConfiguration#WINDOWING_MODE_UNDEFINED} and there is a stack on the
    598      * display in that windowing mode.
    599      */
    600     void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
    601         // TODO: Maybe only allow split-screen windowing modes?
    602         final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED
    603                 ? mDisplayContent.getTopStackInWindowingMode(targetWindowingMode)
    604                 : null;
    605         final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack();
    606         boolean visibleAndValid = visible && stack != null && dockedStack != null;
    607 
    608         // Ensure an old dim that was shown for the docked stack divider is removed so we don't end
    609         // up with dim layers that can no longer be removed.
    610         if (mDimmedStack != null && mDimmedStack != stack) {
    611             mDimmedStack.stopDimming();
    612             mDimmedStack = null;
    613         }
    614 
    615         if (visibleAndValid) {
    616             mDimmedStack = stack;
    617             stack.dim(alpha);
    618         }
    619         if (!visibleAndValid && stack != null) {
    620             mDimmedStack = null;
    621             stack.stopDimming();
    622         }
    623     }
    624 
    625     /**
    626      * @return The layer used for dimming the apps when dismissing docked/fullscreen stack. Just
    627      *         above all application surfaces.
    628      */
    629     private int getResizeDimLayer() {
    630         return (mWindow != null) ? mWindow.mLayer - 1 : LAYER_OFFSET_DIM;
    631     }
    632 
    633     /**
    634      * Notifies the docked stack divider controller of a visibility change that happens without
    635      * an animation.
    636      */
    637     void notifyAppVisibilityChanged() {
    638         checkMinimizeChanged(false /* animate */);
    639     }
    640 
    641     void notifyAppTransitionStarting(ArraySet<AppWindowToken> openingApps, int appTransition) {
    642         final boolean wasMinimized = mMinimizedDock;
    643         checkMinimizeChanged(true /* animate */);
    644 
    645         // We were minimized, and now we are still minimized, but somebody is trying to launch an
    646         // app in docked stack, better show recent apps so we actually get unminimized! However do
    647         // not do this if keyguard is dismissed such as when the device is unlocking. This catches
    648         // any case that was missed in ActivityStarter.postStartActivityUncheckedProcessing because
    649         // we couldn't retrace the launch of the app in the docked stack to the launch from
    650         // homescreen.
    651         if (wasMinimized && mMinimizedDock && containsAppInDockedStack(openingApps)
    652                 && appTransition != TRANSIT_NONE &&
    653                 !AppTransition.isKeyguardGoingAwayTransit(appTransition)) {
    654             if (mService.mAmInternal.isRecentsComponentHomeActivity(mService.mCurrentUserId)) {
    655                 // When the home activity is the recents component and we are already minimized,
    656                 // then there is nothing to do here since home is already visible
    657             } else {
    658                 mService.showRecentApps();
    659             }
    660         }
    661     }
    662 
    663     /**
    664      * @return true if {@param apps} contains an activity in the docked stack, false otherwise.
    665      */
    666     private boolean containsAppInDockedStack(ArraySet<AppWindowToken> apps) {
    667         for (int i = apps.size() - 1; i >= 0; i--) {
    668             final AppWindowToken token = apps.valueAt(i);
    669             if (token.getTask() != null && token.inSplitScreenPrimaryWindowingMode()) {
    670                 return true;
    671             }
    672         }
    673         return false;
    674     }
    675 
    676     boolean isMinimizedDock() {
    677         return mMinimizedDock;
    678     }
    679 
    680     void checkMinimizeChanged(boolean animate) {
    681         if (mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() == null) {
    682             return;
    683         }
    684         final TaskStack homeStack = mDisplayContent.getHomeStack();
    685         if (homeStack == null) {
    686             return;
    687         }
    688         final Task homeTask = homeStack.findHomeTask();
    689         if (homeTask == null || !isWithinDisplay(homeTask)) {
    690             return;
    691         }
    692 
    693         // Do not minimize when dock is already minimized while keyguard is showing and not
    694         // occluded such as unlocking the screen
    695         if (mMinimizedDock && mService.mKeyguardOrAodShowingOnDefaultDisplay) {
    696             return;
    697         }
    698         final TaskStack topSecondaryStack = mDisplayContent.getTopStackInWindowingMode(
    699                 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
    700         final RecentsAnimationController recentsAnim = mService.getRecentsAnimationController();
    701         final boolean minimizedForRecentsAnimation = recentsAnim != null &&
    702                 recentsAnim.isSplitScreenMinimized();
    703         boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
    704         if (homeVisible && topSecondaryStack != null) {
    705             // Home should only be considered visible if it is greater or equal to the top secondary
    706             // stack in terms of z-order.
    707             homeVisible = homeStack.compareTo(topSecondaryStack) >= 0;
    708         }
    709         setMinimizedDockedStack(homeVisible || minimizedForRecentsAnimation, animate);
    710     }
    711 
    712     private boolean isWithinDisplay(Task task) {
    713         task.getBounds(mTmpRect);
    714         mDisplayContent.getBounds(mTmpRect2);
    715         return mTmpRect.intersect(mTmpRect2);
    716     }
    717 
    718     /**
    719      * Sets whether the docked stack is currently in a minimized state, i.e. all the tasks in the
    720      * docked stack are heavily clipped so you can only see a minimal peek state.
    721      *
    722      * @param minimizedDock Whether the docked stack is currently minimized.
    723      * @param animate Whether to animate the change.
    724      */
    725     private void setMinimizedDockedStack(boolean minimizedDock, boolean animate) {
    726         final boolean wasMinimized = mMinimizedDock;
    727         mMinimizedDock = minimizedDock;
    728         if (minimizedDock == wasMinimized) {
    729             return;
    730         }
    731 
    732         final boolean imeChanged = clearImeAdjustAnimation();
    733         boolean minimizedChange = false;
    734         if (isHomeStackResizable()) {
    735             notifyDockedStackMinimizedChanged(minimizedDock, animate,
    736                     true /* isHomeStackResizable */);
    737             minimizedChange = true;
    738         } else {
    739             if (minimizedDock) {
    740                 if (animate) {
    741                     startAdjustAnimation(0f, 1f);
    742                 } else {
    743                     minimizedChange |= setMinimizedDockedStack(true);
    744                 }
    745             } else {
    746                 if (animate) {
    747                     startAdjustAnimation(1f, 0f);
    748                 } else {
    749                     minimizedChange |= setMinimizedDockedStack(false);
    750                 }
    751             }
    752         }
    753         if (imeChanged || minimizedChange) {
    754             if (imeChanged && !minimizedChange) {
    755                 Slog.d(TAG, "setMinimizedDockedStack: IME adjust changed due to minimizing,"
    756                         + " minimizedDock=" + minimizedDock
    757                         + " minimizedChange=" + minimizedChange);
    758             }
    759             mService.mWindowPlacerLocked.performSurfacePlacement();
    760         }
    761     }
    762 
    763     private boolean clearImeAdjustAnimation() {
    764         final boolean changed = mDisplayContent.clearImeAdjustAnimation();
    765         mAnimatingForIme = false;
    766         return changed;
    767     }
    768 
    769     private void startAdjustAnimation(float from, float to) {
    770         mAnimatingForMinimizedDockedStack = true;
    771         mAnimationStarted = false;
    772         mAnimationStart = from;
    773         mAnimationTarget = to;
    774     }
    775 
    776     private void startImeAdjustAnimation(
    777             boolean adjustedForIme, boolean adjustedForDivider, WindowState imeWin) {
    778 
    779         // If we're not in an animation, the starting point depends on whether we're adjusted
    780         // or not. If we're already in an animation, we start from where the current animation
    781         // left off, so that the motion doesn't look discontinuous.
    782         if (!mAnimatingForIme) {
    783             mAnimationStart = mAdjustedForIme ? 1 : 0;
    784             mDividerAnimationStart = mAdjustedForDivider ? 1 : 0;
    785             mLastAnimationProgress = mAnimationStart;
    786             mLastDividerProgress = mDividerAnimationStart;
    787         } else {
    788             mAnimationStart = mLastAnimationProgress;
    789             mDividerAnimationStart = mLastDividerProgress;
    790         }
    791         mAnimatingForIme = true;
    792         mAnimationStarted = false;
    793         mAnimationTarget = adjustedForIme ? 1 : 0;
    794         mDividerAnimationTarget = adjustedForDivider ? 1 : 0;
    795 
    796         mDisplayContent.beginImeAdjustAnimation();
    797 
    798         // We put all tasks into drag resizing mode - wait until all of them have completed the
    799         // drag resizing switch.
    800         if (!mService.mWaitingForDrawn.isEmpty()) {
    801             mService.mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT);
    802             mService.mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT,
    803                     IME_ADJUST_DRAWN_TIMEOUT);
    804             mAnimationStartDelayed = true;
    805             if (imeWin != null) {
    806 
    807                 // There might be an old window delaying the animation start - clear it.
    808                 if (mDelayedImeWin != null) {
    809                     mDelayedImeWin.endDelayingAnimationStart();
    810                 }
    811                 mDelayedImeWin = imeWin;
    812                 imeWin.startDelayingAnimationStart();
    813             }
    814 
    815             // If we are already waiting for something to be drawn, clear out the old one so it
    816             // still gets executed.
    817             // TODO: Have a real system where we can wait on different windows to be drawn with
    818             // different callbacks.
    819             if (mService.mWaitingForDrawnCallback != null) {
    820                 mService.mWaitingForDrawnCallback.run();
    821             }
    822             mService.mWaitingForDrawnCallback = () -> {
    823                 synchronized (mService.mWindowMap) {
    824                     mAnimationStartDelayed = false;
    825                     if (mDelayedImeWin != null) {
    826                         mDelayedImeWin.endDelayingAnimationStart();
    827                     }
    828                     // If the adjust status changed since this was posted, only notify
    829                     // the new states and don't animate.
    830                     long duration = 0;
    831                     if (mAdjustedForIme == adjustedForIme
    832                             && mAdjustedForDivider == adjustedForDivider) {
    833                         duration = IME_ADJUST_ANIM_DURATION;
    834                     } else {
    835                         Slog.w(TAG, "IME adjust changed while waiting for drawn:"
    836                                 + " adjustedForIme=" + adjustedForIme
    837                                 + " adjustedForDivider=" + adjustedForDivider
    838                                 + " mAdjustedForIme=" + mAdjustedForIme
    839                                 + " mAdjustedForDivider=" + mAdjustedForDivider);
    840                     }
    841                     notifyAdjustedForImeChanged(
    842                             mAdjustedForIme || mAdjustedForDivider, duration);
    843                 }
    844             };
    845         } else {
    846             notifyAdjustedForImeChanged(
    847                     adjustedForIme || adjustedForDivider, IME_ADJUST_ANIM_DURATION);
    848         }
    849     }
    850 
    851     private boolean setMinimizedDockedStack(boolean minimized) {
    852         final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
    853         notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable());
    854         return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f);
    855     }
    856 
    857     private boolean isAnimationMaximizing() {
    858         return mAnimationTarget == 0f;
    859     }
    860 
    861     public boolean animate(long now) {
    862         if (mWindow == null) {
    863             return false;
    864         }
    865         if (mAnimatingForMinimizedDockedStack) {
    866             return animateForMinimizedDockedStack(now);
    867         } else if (mAnimatingForIme) {
    868             return animateForIme(now);
    869         }
    870         return false;
    871     }
    872 
    873     private boolean animateForIme(long now) {
    874         if (!mAnimationStarted || mAnimationStartDelayed) {
    875             mAnimationStarted = true;
    876             mAnimationStartTime = now;
    877             mAnimationDuration = (long)
    878                     (IME_ADJUST_ANIM_DURATION * mService.getWindowAnimationScaleLocked());
    879         }
    880         float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration);
    881         t = (mAnimationTarget == 1f ? IME_ADJUST_ENTRY_INTERPOLATOR : TOUCH_RESPONSE_INTERPOLATOR)
    882                 .getInterpolation(t);
    883         final boolean updated =
    884                 mDisplayContent.animateForIme(t, mAnimationTarget, mDividerAnimationTarget);
    885         if (updated) {
    886             mService.mWindowPlacerLocked.performSurfacePlacement();
    887         }
    888         if (t >= 1.0f) {
    889             mLastAnimationProgress = mAnimationTarget;
    890             mLastDividerProgress = mDividerAnimationTarget;
    891             mAnimatingForIme = false;
    892             return false;
    893         } else {
    894             return true;
    895         }
    896     }
    897 
    898     private boolean animateForMinimizedDockedStack(long now) {
    899         final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
    900         if (!mAnimationStarted) {
    901             mAnimationStarted = true;
    902             mAnimationStartTime = now;
    903             notifyDockedStackMinimizedChanged(mMinimizedDock, true /* animate */,
    904                     isHomeStackResizable() /* isHomeStackResizable */);
    905         }
    906         float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration);
    907         t = (isAnimationMaximizing() ? TOUCH_RESPONSE_INTERPOLATOR : mMinimizedDockInterpolator)
    908                 .getInterpolation(t);
    909         if (stack != null) {
    910             if (stack.setAdjustedForMinimizedDock(getMinimizeAmount(stack, t))) {
    911                 mService.mWindowPlacerLocked.performSurfacePlacement();
    912             }
    913         }
    914         if (t >= 1.0f) {
    915             mAnimatingForMinimizedDockedStack = false;
    916             return false;
    917         } else {
    918             return true;
    919         }
    920     }
    921 
    922     float getInterpolatedAnimationValue(float t) {
    923         return t * mAnimationTarget + (1 - t) * mAnimationStart;
    924     }
    925 
    926     float getInterpolatedDividerValue(float t) {
    927         return t * mDividerAnimationTarget + (1 - t) * mDividerAnimationStart;
    928     }
    929 
    930     /**
    931      * Gets the amount how much to minimize a stack depending on the interpolated fraction t.
    932      */
    933     private float getMinimizeAmount(TaskStack stack, float t) {
    934         final float naturalAmount = getInterpolatedAnimationValue(t);
    935         if (isAnimationMaximizing()) {
    936             return adjustMaximizeAmount(stack, t, naturalAmount);
    937         } else {
    938             return naturalAmount;
    939         }
    940     }
    941 
    942     /**
    943      * When maximizing the stack during a clip reveal transition, this adjusts the minimize amount
    944      * during the transition such that the edge of the clip reveal rect is met earlier in the
    945      * transition so we don't create a visible "hole", but only if both the clip reveal and the
    946      * docked stack divider start from about the same portion on the screen.
    947      */
    948     private float adjustMaximizeAmount(TaskStack stack, float t, float naturalAmount) {
    949         if (mMaximizeMeetFraction == 1f) {
    950             return naturalAmount;
    951         }
    952         final int minimizeDistance = stack.getMinimizeDistance();
    953         float startPrime = mService.mAppTransition.getLastClipRevealMaxTranslation()
    954                 / (float) minimizeDistance;
    955         final float amountPrime = t * mAnimationTarget + (1 - t) * startPrime;
    956         final float t2 = Math.min(t / mMaximizeMeetFraction, 1);
    957         return amountPrime * t2 + naturalAmount * (1 - t2);
    958     }
    959 
    960     /**
    961      * Retrieves the animation fraction at which the docked stack has to meet the clip reveal
    962      * edge. See {@link #adjustMaximizeAmount}.
    963      */
    964     private float getClipRevealMeetFraction(TaskStack stack) {
    965         if (!isAnimationMaximizing() || stack == null ||
    966                 !mService.mAppTransition.hadClipRevealAnimation()) {
    967             return 1f;
    968         }
    969         final int minimizeDistance = stack.getMinimizeDistance();
    970         final float fraction = Math.abs(mService.mAppTransition.getLastClipRevealMaxTranslation())
    971                 / (float) minimizeDistance;
    972         final float t = Math.max(0, Math.min(1, (fraction - CLIP_REVEAL_MEET_FRACTION_MIN)
    973                 / (CLIP_REVEAL_MEET_FRACTION_MAX - CLIP_REVEAL_MEET_FRACTION_MIN)));
    974         return CLIP_REVEAL_MEET_EARLIEST
    975                 + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST);
    976     }
    977 
    978     public String toShortString() {
    979         return TAG;
    980     }
    981 
    982     WindowState getWindow() {
    983         return mWindow;
    984     }
    985 
    986     void dump(String prefix, PrintWriter pw) {
    987         pw.println(prefix + "DockedStackDividerController");
    988         pw.println(prefix + "  mLastVisibility=" + mLastVisibility);
    989         pw.println(prefix + "  mMinimizedDock=" + mMinimizedDock);
    990         pw.println(prefix + "  mAdjustedForIme=" + mAdjustedForIme);
    991         pw.println(prefix + "  mAdjustedForDivider=" + mAdjustedForDivider);
    992     }
    993 
    994     void writeToProto(ProtoOutputStream proto, long fieldId) {
    995         final long token = proto.start(fieldId);
    996         proto.write(MINIMIZED_DOCK, mMinimizedDock);
    997         proto.end(token);
    998     }
    999 }
   1000