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