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