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
17package com.android.server.wm;
18
19import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
20import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
21import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
22import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
23import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
24import static android.view.Surface.ROTATION_270;
25import static android.view.Surface.ROTATION_90;
26import static android.view.WindowManager.DOCKED_BOTTOM;
27import static android.view.WindowManager.DOCKED_LEFT;
28import static android.view.WindowManager.DOCKED_RIGHT;
29import static android.view.WindowManager.DOCKED_TOP;
30import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION;
31import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR;
32import static com.android.server.wm.AppTransition.TRANSIT_NONE;
33import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
34import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
35import static com.android.server.wm.WindowManagerService.H.NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED;
36import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
37
38import android.content.Context;
39import android.content.res.Configuration;
40import android.graphics.Rect;
41import android.os.RemoteCallbackList;
42import android.os.RemoteException;
43import android.util.ArraySet;
44import android.util.Slog;
45import android.view.DisplayInfo;
46import android.view.IDockedStackListener;
47import android.view.animation.AnimationUtils;
48import android.view.animation.Interpolator;
49import android.view.animation.PathInterpolator;
50import android.view.inputmethod.InputMethodManagerInternal;
51
52import com.android.internal.policy.DividerSnapAlgorithm;
53import com.android.internal.policy.DockedDividerUtils;
54import com.android.server.LocalServices;
55import com.android.server.wm.DimLayer.DimLayerUser;
56import com.android.server.wm.WindowManagerService.H;
57
58import java.io.PrintWriter;
59
60/**
61 * Keeps information about the docked stack divider.
62 */
63public 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
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
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
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
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
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
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
271    void onConfigurationChanged() {
272        loadDimens();
273    }
274
275    boolean isResizing() {
276        return mResizing;
277    }
278
279    int getContentWidth() {
280        return mDividerWindowWidth - 2 * mDividerInsets;
281    }
282
283    int getContentInsets() {
284        return mDividerInsets;
285    }
286
287    int getContentWidthInactive() {
288        return mDividerWindowWidthInactive;
289    }
290
291    void setResizing(boolean resizing) {
292        if (mResizing != resizing) {
293            mResizing = resizing;
294            resetDragResizingChangeReported();
295        }
296    }
297
298    void setTouchRegion(Rect touchRegion) {
299        mTouchRegion.set(touchRegion);
300    }
301
302    void getTouchRegion(Rect outRegion) {
303        outRegion.set(mTouchRegion);
304        outRegion.offset(mWindow.getFrameLw().left, mWindow.getFrameLw().top);
305    }
306
307    private void resetDragResizingChangeReported() {
308        mDisplayContent.forAllWindows(WindowState::resetDragResizingChangeReported,
309                true /* traverseTopToBottom */ );
310    }
311
312    void setWindow(WindowState window) {
313        mWindow = window;
314        reevaluateVisibility(false);
315    }
316
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
335    private boolean wasVisible() {
336        return mLastVisibility;
337    }
338
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
356    int getImeHeightAdjustedFor() {
357        return mImeHeight;
358    }
359
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
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
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        }
431        setMinimizedDockedStack(false, false /* animate */);
432    }
433
434    /**
435     * Resets the state that IME hide has been requested. See {@link #isImeHideRequested}.
436     */
437    void resetImeHideRequested() {
438        mImeHideRequested = false;
439    }
440
441    /**
442     * The docked stack divider controller makes sure the IME gets hidden when attaching the docked
443     * stack, to avoid animation problems. This flag indicates whether the request to hide the IME
444     * has been sent in an asynchronous manner, and the IME should be treated as hidden already.
445     *
446     * @return whether IME hide request has been sent
447     */
448    boolean isImeHideRequested() {
449        return mImeHideRequested;
450    }
451
452    private void notifyDockedStackMinimizedChanged(boolean minimizedDock, boolean animate,
453            boolean isHomeStackResizable) {
454        long animDuration = 0;
455        if (animate) {
456            final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
457            final long transitionDuration = isAnimationMaximizing()
458                    ? mService.mAppTransition.getLastClipRevealTransitionDuration()
459                    : DEFAULT_APP_TRANSITION_DURATION;
460            mAnimationDuration = (long)
461                    (transitionDuration * mService.getTransitionAnimationScaleLocked());
462            mMaximizeMeetFraction = getClipRevealMeetFraction(stack);
463            animDuration = (long) (mAnimationDuration * mMaximizeMeetFraction);
464        }
465        mService.mH.removeMessages(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED);
466        mService.mH.obtainMessage(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED,
467                minimizedDock ? 1 : 0, 0).sendToTarget();
468        final int size = mDockedStackListeners.beginBroadcast();
469        for (int i = 0; i < size; ++i) {
470            final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
471            try {
472                listener.onDockedStackMinimizedChanged(minimizedDock, animDuration,
473                        isHomeStackResizable);
474            } catch (RemoteException e) {
475                Slog.e(TAG_WM, "Error delivering minimized dock changed event.", e);
476            }
477        }
478        mDockedStackListeners.finishBroadcast();
479    }
480
481    void notifyDockSideChanged(int newDockSide) {
482        final int size = mDockedStackListeners.beginBroadcast();
483        for (int i = 0; i < size; ++i) {
484            final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
485            try {
486                listener.onDockSideChanged(newDockSide);
487            } catch (RemoteException e) {
488                Slog.e(TAG_WM, "Error delivering dock side changed event.", e);
489            }
490        }
491        mDockedStackListeners.finishBroadcast();
492    }
493
494    private void notifyAdjustedForImeChanged(boolean adjustedForIme, long animDuration) {
495        final int size = mDockedStackListeners.beginBroadcast();
496        for (int i = 0; i < size; ++i) {
497            final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
498            try {
499                listener.onAdjustedForImeChanged(adjustedForIme, animDuration);
500            } catch (RemoteException e) {
501                Slog.e(TAG_WM, "Error delivering adjusted for ime changed event.", e);
502            }
503        }
504        mDockedStackListeners.finishBroadcast();
505    }
506
507    void registerDockedStackListener(IDockedStackListener listener) {
508        mDockedStackListeners.register(listener);
509        notifyDockedDividerVisibilityChanged(wasVisible());
510        notifyDockedStackExistsChanged(mDisplayContent.getDockedStackIgnoringVisibility() != null);
511        notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */,
512                isHomeStackResizable());
513        notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
514
515    }
516
517    void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
518        mService.openSurfaceTransaction();
519        final TaskStack stack = mDisplayContent.getStackById(targetStackId);
520        final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
521        boolean visibleAndValid = visible && stack != null && dockedStack != null;
522        if (visibleAndValid) {
523            stack.getDimBounds(mTmpRect);
524            if (mTmpRect.height() > 0 && mTmpRect.width() > 0) {
525                mDimLayer.setBounds(mTmpRect);
526                mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
527            } else {
528                visibleAndValid = false;
529            }
530        }
531        if (!visibleAndValid) {
532            mDimLayer.hide();
533        }
534        mService.closeSurfaceTransaction();
535    }
536
537    /**
538     * @return The layer used for dimming the apps when dismissing docked/fullscreen stack. Just
539     *         above all application surfaces.
540     */
541    private int getResizeDimLayer() {
542        return (mWindow != null) ? mWindow.mLayer - 1 : LAYER_OFFSET_DIM;
543    }
544
545    /**
546     * Notifies the docked stack divider controller of a visibility change that happens without
547     * an animation.
548     */
549    void notifyAppVisibilityChanged() {
550        checkMinimizeChanged(false /* animate */);
551    }
552
553    void notifyAppTransitionStarting(ArraySet<AppWindowToken> openingApps, int appTransition) {
554        final boolean wasMinimized = mMinimizedDock;
555        checkMinimizeChanged(true /* animate */);
556
557        // We were minimized, and now we are still minimized, but somebody is trying to launch an
558        // app in docked stack, better show recent apps so we actually get unminimized! However do
559        // not do this if keyguard is dismissed such as when the device is unlocking. This catches
560        // any case that was missed in ActivityStarter.postStartActivityUncheckedProcessing because
561        // we couldn't retrace the launch of the app in the docked stack to the launch from
562        // homescreen.
563        if (wasMinimized && mMinimizedDock && containsAppInDockedStack(openingApps)
564                && appTransition != TRANSIT_NONE &&
565                !AppTransition.isKeyguardGoingAwayTransit(appTransition)) {
566            mService.showRecentApps(true /* fromHome */);
567        }
568    }
569
570    /**
571     * @return true if {@param apps} contains an activity in the docked stack, false otherwise.
572     */
573    private boolean containsAppInDockedStack(ArraySet<AppWindowToken> apps) {
574        for (int i = apps.size() - 1; i >= 0; i--) {
575            final AppWindowToken token = apps.valueAt(i);
576            if (token.getTask() != null && token.getTask().mStack.mStackId == DOCKED_STACK_ID) {
577                return true;
578            }
579        }
580        return false;
581    }
582
583    boolean isMinimizedDock() {
584        return mMinimizedDock;
585    }
586
587    private void checkMinimizeChanged(boolean animate) {
588        if (mDisplayContent.getDockedStackIgnoringVisibility() == null) {
589            return;
590        }
591        final TaskStack homeStack = mDisplayContent.getHomeStack();
592        if (homeStack == null) {
593            return;
594        }
595        final Task homeTask = homeStack.findHomeTask();
596        if (homeTask == null || !isWithinDisplay(homeTask)) {
597            return;
598        }
599
600        // Do not minimize when dock is already minimized while keyguard is showing and not
601        // occluded such as unlocking the screen
602        if (mMinimizedDock && mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
603            return;
604        }
605        final TaskStack fullscreenStack =
606                mDisplayContent.getStackById(FULLSCREEN_WORKSPACE_STACK_ID);
607        final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
608        final boolean homeBehind = (fullscreenStack != null && fullscreenStack.isVisible())
609                || (homeStack.hasMultipleTaskWithHomeTaskNotTop());
610        setMinimizedDockedStack(homeVisible && !homeBehind, animate);
611    }
612
613    private boolean isWithinDisplay(Task task) {
614        task.mStack.getBounds(mTmpRect);
615        mDisplayContent.getLogicalDisplayRect(mTmpRect2);
616        return mTmpRect.intersect(mTmpRect2);
617    }
618
619    /**
620     * Sets whether the docked stack is currently in a minimized state, i.e. all the tasks in the
621     * docked stack are heavily clipped so you can only see a minimal peek state.
622     *
623     * @param minimizedDock Whether the docked stack is currently minimized.
624     * @param animate Whether to animate the change.
625     */
626    private void setMinimizedDockedStack(boolean minimizedDock, boolean animate) {
627        final boolean wasMinimized = mMinimizedDock;
628        mMinimizedDock = minimizedDock;
629        if (minimizedDock == wasMinimized) {
630            return;
631        }
632
633        final boolean imeChanged = clearImeAdjustAnimation();
634        boolean minimizedChange = false;
635        if (isHomeStackResizable()) {
636            notifyDockedStackMinimizedChanged(minimizedDock, true /* animate */,
637                    true /* isHomeStackResizable */);
638            minimizedChange = true;
639        } else {
640            if (minimizedDock) {
641                if (animate) {
642                    startAdjustAnimation(0f, 1f);
643                } else {
644                    minimizedChange |= setMinimizedDockedStack(true);
645                }
646            } else {
647                if (animate) {
648                    startAdjustAnimation(1f, 0f);
649                } else {
650                    minimizedChange |= setMinimizedDockedStack(false);
651                }
652            }
653        }
654        if (imeChanged || minimizedChange) {
655            if (imeChanged && !minimizedChange) {
656                Slog.d(TAG, "setMinimizedDockedStack: IME adjust changed due to minimizing,"
657                        + " minimizedDock=" + minimizedDock
658                        + " minimizedChange=" + minimizedChange);
659            }
660            mService.mWindowPlacerLocked.performSurfacePlacement();
661        }
662    }
663
664    private boolean clearImeAdjustAnimation() {
665        final boolean changed = mDisplayContent.clearImeAdjustAnimation();
666        mAnimatingForIme = false;
667        return changed;
668    }
669
670    private void startAdjustAnimation(float from, float to) {
671        mAnimatingForMinimizedDockedStack = true;
672        mAnimationStarted = false;
673        mAnimationStart = from;
674        mAnimationTarget = to;
675    }
676
677    private void startImeAdjustAnimation(
678            boolean adjustedForIme, boolean adjustedForDivider, WindowState imeWin) {
679
680        // If we're not in an animation, the starting point depends on whether we're adjusted
681        // or not. If we're already in an animation, we start from where the current animation
682        // left off, so that the motion doesn't look discontinuous.
683        if (!mAnimatingForIme) {
684            mAnimationStart = mAdjustedForIme ? 1 : 0;
685            mDividerAnimationStart = mAdjustedForDivider ? 1 : 0;
686            mLastAnimationProgress = mAnimationStart;
687            mLastDividerProgress = mDividerAnimationStart;
688        } else {
689            mAnimationStart = mLastAnimationProgress;
690            mDividerAnimationStart = mLastDividerProgress;
691        }
692        mAnimatingForIme = true;
693        mAnimationStarted = false;
694        mAnimationTarget = adjustedForIme ? 1 : 0;
695        mDividerAnimationTarget = adjustedForDivider ? 1 : 0;
696
697        mDisplayContent.beginImeAdjustAnimation();
698
699        // We put all tasks into drag resizing mode - wait until all of them have completed the
700        // drag resizing switch.
701        if (!mService.mWaitingForDrawn.isEmpty()) {
702            mService.mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT);
703            mService.mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT,
704                    IME_ADJUST_DRAWN_TIMEOUT);
705            mAnimationStartDelayed = true;
706            if (imeWin != null) {
707
708                // There might be an old window delaying the animation start - clear it.
709                if (mDelayedImeWin != null) {
710                    mDelayedImeWin.mWinAnimator.endDelayingAnimationStart();
711                }
712                mDelayedImeWin = imeWin;
713                imeWin.mWinAnimator.startDelayingAnimationStart();
714            }
715
716            // If we are already waiting for something to be drawn, clear out the old one so it
717            // still gets executed.
718            // TODO: Have a real system where we can wait on different windows to be drawn with
719            // different callbacks.
720            if (mService.mWaitingForDrawnCallback != null) {
721                mService.mWaitingForDrawnCallback.run();
722            }
723            mService.mWaitingForDrawnCallback = () -> {
724                mAnimationStartDelayed = false;
725                if (mDelayedImeWin != null) {
726                    mDelayedImeWin.mWinAnimator.endDelayingAnimationStart();
727                }
728                // If the adjust status changed since this was posted, only notify
729                // the new states and don't animate.
730                long duration = 0;
731                if (mAdjustedForIme == adjustedForIme
732                        && mAdjustedForDivider == adjustedForDivider) {
733                    duration = IME_ADJUST_ANIM_DURATION;
734                } else {
735                    Slog.w(TAG, "IME adjust changed while waiting for drawn:"
736                            + " adjustedForIme=" + adjustedForIme
737                            + " adjustedForDivider=" + adjustedForDivider
738                            + " mAdjustedForIme=" + mAdjustedForIme
739                            + " mAdjustedForDivider=" + mAdjustedForDivider);
740                }
741                notifyAdjustedForImeChanged(
742                        mAdjustedForIme || mAdjustedForDivider, duration);
743            };
744        } else {
745            notifyAdjustedForImeChanged(
746                    adjustedForIme || adjustedForDivider, IME_ADJUST_ANIM_DURATION);
747        }
748    }
749
750    private boolean setMinimizedDockedStack(boolean minimized) {
751        final TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility();
752        notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable());
753        return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f);
754    }
755
756    private boolean isAnimationMaximizing() {
757        return mAnimationTarget == 0f;
758    }
759
760    public boolean animate(long now) {
761        if (mWindow == null) {
762            return false;
763        }
764        if (mAnimatingForMinimizedDockedStack) {
765            return animateForMinimizedDockedStack(now);
766        } else if (mAnimatingForIme) {
767            return animateForIme(now);
768        } else {
769            if (mDimLayer != null && mDimLayer.isDimming()) {
770                mDimLayer.setLayer(getResizeDimLayer());
771            }
772            return false;
773        }
774    }
775
776    private boolean animateForIme(long now) {
777        if (!mAnimationStarted || mAnimationStartDelayed) {
778            mAnimationStarted = true;
779            mAnimationStartTime = now;
780            mAnimationDuration = (long)
781                    (IME_ADJUST_ANIM_DURATION * mService.getWindowAnimationScaleLocked());
782        }
783        float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration);
784        t = (mAnimationTarget == 1f ? IME_ADJUST_ENTRY_INTERPOLATOR : TOUCH_RESPONSE_INTERPOLATOR)
785                .getInterpolation(t);
786        final boolean updated =
787                mDisplayContent.animateForIme(t, mAnimationTarget, mDividerAnimationTarget);
788        if (updated) {
789            mService.mWindowPlacerLocked.performSurfacePlacement();
790        }
791        if (t >= 1.0f) {
792            mLastAnimationProgress = mAnimationTarget;
793            mLastDividerProgress = mDividerAnimationTarget;
794            mAnimatingForIme = false;
795            return false;
796        } else {
797            return true;
798        }
799    }
800
801    private boolean animateForMinimizedDockedStack(long now) {
802        final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
803        if (!mAnimationStarted) {
804            mAnimationStarted = true;
805            mAnimationStartTime = now;
806            notifyDockedStackMinimizedChanged(mMinimizedDock, true /* animate */,
807                    isHomeStackResizable() /* isHomeStackResizable */);
808        }
809        float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration);
810        t = (isAnimationMaximizing() ? TOUCH_RESPONSE_INTERPOLATOR : mMinimizedDockInterpolator)
811                .getInterpolation(t);
812        if (stack != null) {
813            if (stack.setAdjustedForMinimizedDock(getMinimizeAmount(stack, t))) {
814                mService.mWindowPlacerLocked.performSurfacePlacement();
815            }
816        }
817        if (t >= 1.0f) {
818            mAnimatingForMinimizedDockedStack = false;
819            return false;
820        } else {
821            return true;
822        }
823    }
824
825    float getInterpolatedAnimationValue(float t) {
826        return t * mAnimationTarget + (1 - t) * mAnimationStart;
827    }
828
829    float getInterpolatedDividerValue(float t) {
830        return t * mDividerAnimationTarget + (1 - t) * mDividerAnimationStart;
831    }
832
833    /**
834     * Gets the amount how much to minimize a stack depending on the interpolated fraction t.
835     */
836    private float getMinimizeAmount(TaskStack stack, float t) {
837        final float naturalAmount = getInterpolatedAnimationValue(t);
838        if (isAnimationMaximizing()) {
839            return adjustMaximizeAmount(stack, t, naturalAmount);
840        } else {
841            return naturalAmount;
842        }
843    }
844
845    /**
846     * When maximizing the stack during a clip reveal transition, this adjusts the minimize amount
847     * during the transition such that the edge of the clip reveal rect is met earlier in the
848     * transition so we don't create a visible "hole", but only if both the clip reveal and the
849     * docked stack divider start from about the same portion on the screen.
850     */
851    private float adjustMaximizeAmount(TaskStack stack, float t, float naturalAmount) {
852        if (mMaximizeMeetFraction == 1f) {
853            return naturalAmount;
854        }
855        final int minimizeDistance = stack.getMinimizeDistance();
856        float startPrime = mService.mAppTransition.getLastClipRevealMaxTranslation()
857                / (float) minimizeDistance;
858        final float amountPrime = t * mAnimationTarget + (1 - t) * startPrime;
859        final float t2 = Math.min(t / mMaximizeMeetFraction, 1);
860        return amountPrime * t2 + naturalAmount * (1 - t2);
861    }
862
863    /**
864     * Retrieves the animation fraction at which the docked stack has to meet the clip reveal
865     * edge. See {@link #adjustMaximizeAmount}.
866     */
867    private float getClipRevealMeetFraction(TaskStack stack) {
868        if (!isAnimationMaximizing() || stack == null ||
869                !mService.mAppTransition.hadClipRevealAnimation()) {
870            return 1f;
871        }
872        final int minimizeDistance = stack.getMinimizeDistance();
873        final float fraction = Math.abs(mService.mAppTransition.getLastClipRevealMaxTranslation())
874                / (float) minimizeDistance;
875        final float t = Math.max(0, Math.min(1, (fraction - CLIP_REVEAL_MEET_FRACTION_MIN)
876                / (CLIP_REVEAL_MEET_FRACTION_MAX - CLIP_REVEAL_MEET_FRACTION_MIN)));
877        return CLIP_REVEAL_MEET_EARLIEST
878                + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST);
879    }
880
881    @Override
882    public boolean dimFullscreen() {
883        return false;
884    }
885
886    @Override
887    public DisplayInfo getDisplayInfo() {
888        return mDisplayContent.getDisplayInfo();
889    }
890
891    @Override
892    public boolean isAttachedToDisplay() {
893        return mDisplayContent != null;
894    }
895
896    @Override
897    public void getDimBounds(Rect outBounds) {
898        // This dim layer user doesn't need this.
899    }
900
901    @Override
902    public String toShortString() {
903        return TAG;
904    }
905
906    WindowState getWindow() {
907        return mWindow;
908    }
909
910    void dump(String prefix, PrintWriter pw) {
911        pw.println(prefix + "DockedStackDividerController");
912        pw.println(prefix + "  mLastVisibility=" + mLastVisibility);
913        pw.println(prefix + "  mMinimizedDock=" + mMinimizedDock);
914        pw.println(prefix + "  mAdjustedForIme=" + mAdjustedForIme);
915        pw.println(prefix + "  mAdjustedForDivider=" + mAdjustedForDivider);
916        if (mDimLayer.isDimming()) {
917            pw.println(prefix + "  Dim layer is dimming: ");
918            mDimLayer.printTo(prefix + "    ", pw);
919        }
920    }
921}
922