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 
DockedStackDividerController(WindowManagerService service, DisplayContent displayContent)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 
getSmallestWidthDpForBounds(Rect bounds)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      */
getDockSide(Rect bounds, Rect displayRect, int orientation)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 
getHomeStackBoundsInDockedMode(Rect outBounds)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 
isHomeStackResizable()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 
initSnapAlgorithmForRotations()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 
loadDimens()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 
onConfigurationChanged()321     void onConfigurationChanged() {
322         loadDimens();
323     }
324 
isResizing()325     boolean isResizing() {
326         return mResizing;
327     }
328 
getContentWidth()329     int getContentWidth() {
330         return mDividerWindowWidth - 2 * mDividerInsets;
331     }
332 
getContentInsets()333     int getContentInsets() {
334         return mDividerInsets;
335     }
336 
getContentWidthInactive()337     int getContentWidthInactive() {
338         return mDividerWindowWidthInactive;
339     }
340 
setResizing(boolean resizing)341     void setResizing(boolean resizing) {
342         if (mResizing != resizing) {
343             mResizing = resizing;
344             resetDragResizingChangeReported();
345         }
346     }
347 
setTouchRegion(Rect touchRegion)348     void setTouchRegion(Rect touchRegion) {
349         mTouchRegion.set(touchRegion);
350     }
351 
getTouchRegion(Rect outRegion)352     void getTouchRegion(Rect outRegion) {
353         outRegion.set(mTouchRegion);
354         outRegion.offset(mWindow.getFrameLw().left, mWindow.getFrameLw().top);
355     }
356 
resetDragResizingChangeReported()357     private void resetDragResizingChangeReported() {
358         mDisplayContent.forAllWindows(WindowState::resetDragResizingChangeReported,
359                 true /* traverseTopToBottom */ );
360     }
361 
setWindow(WindowState window)362     void setWindow(WindowState window) {
363         mWindow = window;
364         reevaluateVisibility(false);
365     }
366 
reevaluateVisibility(boolean force)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 
wasVisible()385     private boolean wasVisible() {
386         return mLastVisibility;
387     }
388 
setAdjustedForIme( boolean adjustedForIme, boolean adjustedForDivider, boolean animate, WindowState imeWin, int imeHeight)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 
getImeHeightAdjustedFor()406     int getImeHeightAdjustedFor() {
407         return mImeHeight;
408     }
409 
positionDockedStackedDivider(Rect frame)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 
notifyDockedDividerVisibilityChanged(boolean visible)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      */
canPrimaryStackDockTo(int dockSide)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 
notifyDockedStackExistsChanged(boolean exists)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      */
resetImeHideRequested()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      */
isImeHideRequested()523     boolean isImeHideRequested() {
524         return mImeHideRequested;
525     }
526 
notifyDockedStackMinimizedChanged(boolean minimizedDock, boolean animate, boolean isHomeStackResizable)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 
notifyDockSideChanged(int newDockSide)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 
notifyAdjustedForImeChanged(boolean adjustedForIme, long animDuration)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 
registerDockedStackListener(IDockedStackListener listener)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      */
setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha)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      */
getResizeDimLayer()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      */
notifyAppVisibilityChanged()637     void notifyAppVisibilityChanged() {
638         checkMinimizeChanged(false /* animate */);
639     }
640 
notifyAppTransitionStarting(ArraySet<AppWindowToken> openingApps, int appTransition)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      */
containsAppInDockedStack(ArraySet<AppWindowToken> apps)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 
isMinimizedDock()676     boolean isMinimizedDock() {
677         return mMinimizedDock;
678     }
679 
checkMinimizeChanged(boolean animate)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 
isWithinDisplay(Task task)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      */
setMinimizedDockedStack(boolean minimizedDock, boolean animate)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 
clearImeAdjustAnimation()763     private boolean clearImeAdjustAnimation() {
764         final boolean changed = mDisplayContent.clearImeAdjustAnimation();
765         mAnimatingForIme = false;
766         return changed;
767     }
768 
startAdjustAnimation(float from, float to)769     private void startAdjustAnimation(float from, float to) {
770         mAnimatingForMinimizedDockedStack = true;
771         mAnimationStarted = false;
772         mAnimationStart = from;
773         mAnimationTarget = to;
774     }
775 
startImeAdjustAnimation( boolean adjustedForIme, boolean adjustedForDivider, WindowState imeWin)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 
setMinimizedDockedStack(boolean minimized)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 
isAnimationMaximizing()857     private boolean isAnimationMaximizing() {
858         return mAnimationTarget == 0f;
859     }
860 
animate(long now)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 
animateForIme(long now)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 
animateForMinimizedDockedStack(long now)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 
getInterpolatedAnimationValue(float t)922     float getInterpolatedAnimationValue(float t) {
923         return t * mAnimationTarget + (1 - t) * mAnimationStart;
924     }
925 
getInterpolatedDividerValue(float t)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      */
getMinimizeAmount(TaskStack stack, float t)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      */
adjustMaximizeAmount(TaskStack stack, float t, float naturalAmount)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      */
getClipRevealMeetFraction(TaskStack stack)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 
toShortString()978     public String toShortString() {
979         return TAG;
980     }
981 
getWindow()982     WindowState getWindow() {
983         return mWindow;
984     }
985 
dump(String prefix, PrintWriter pw)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 
writeToProto(ProtoOutputStream proto, long fieldId)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