DockedStackDividerController.java revision 19d9a8f47625319406f593d4ec71de0c8df1fcfe
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.view.WindowManager.DOCKED_BOTTOM;
23import static android.view.WindowManager.DOCKED_LEFT;
24import static android.view.WindowManager.DOCKED_RIGHT;
25import static android.view.WindowManager.DOCKED_TOP;
26import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION;
27import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR;
28import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
29import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
30import static com.android.server.wm.WindowManagerService.H.NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED;
31
32import android.content.Context;
33import android.graphics.Rect;
34import android.os.RemoteCallbackList;
35import android.os.RemoteException;
36import android.util.Slog;
37import android.view.DisplayInfo;
38import android.view.IDockedStackListener;
39import android.view.SurfaceControl;
40import android.view.animation.AnimationUtils;
41import android.view.animation.Interpolator;
42import android.view.animation.PathInterpolator;
43
44import com.android.server.wm.DimLayer.DimLayerUser;
45import com.android.server.wm.WindowManagerService.H;
46
47import java.io.PrintWriter;
48import java.util.ArrayList;
49
50/**
51 * Keeps information about the docked stack divider.
52 */
53public class DockedStackDividerController implements DimLayerUser {
54
55    private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM;
56
57    /**
58     * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip
59     * revealing surface at the earliest.
60     */
61    private static final float CLIP_REVEAL_MEET_EARLIEST = 0.6f;
62
63    /**
64     * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip
65     * revealing surface at the latest.
66     */
67    private static final float CLIP_REVEAL_MEET_LAST = 1f;
68
69    /**
70     * If the app translates at least CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance, we start
71     * meet somewhere between {@link #CLIP_REVEAL_MEET_LAST} and {@link #CLIP_REVEAL_MEET_EARLIEST}.
72     */
73    private static final float CLIP_REVEAL_MEET_FRACTION_MIN = 0.4f;
74
75    /**
76     * If the app translates equals or more than CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance,
77     * we meet at {@link #CLIP_REVEAL_MEET_EARLIEST}.
78     */
79    private static final float CLIP_REVEAL_MEET_FRACTION_MAX = 0.8f;
80
81    private static final Interpolator IME_ADJUST_ENTRY_INTERPOLATOR =
82            new PathInterpolator(0.2f, 0f, 0.1f, 1f);
83
84    private static final long IME_ADJUST_ANIM_DURATION = 280;
85
86    private static final long IME_ADJUST_DRAWN_TIMEOUT = 200;
87
88    private static final int DIVIDER_WIDTH_INACTIVE_DP = 4;
89
90    private final WindowManagerService mService;
91    private final DisplayContent mDisplayContent;
92    private int mDividerWindowWidth;
93    private int mDividerWindowWidthInactive;
94    private int mDividerInsets;
95    private boolean mResizing;
96    private WindowState mWindow;
97    private final Rect mTmpRect = new Rect();
98    private final Rect mTmpRect2 = new Rect();
99    private final Rect mLastRect = new Rect();
100    private boolean mLastVisibility = false;
101    private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners
102            = new RemoteCallbackList<>();
103    private final DimLayer mDimLayer;
104
105    private boolean mMinimizedDock;
106    private boolean mAnimatingForMinimizedDockedStack;
107    private boolean mAnimationStarted;
108    private long mAnimationStartTime;
109    private float mAnimationStart;
110    private float mAnimationTarget;
111    private long mAnimationDuration;
112    private boolean mAnimationStartDelayed;
113    private final Interpolator mMinimizedDockInterpolator;
114    private float mMaximizeMeetFraction;
115    private final Rect mTouchRegion = new Rect();
116    private boolean mAnimatingForIme;
117    private boolean mAdjustedForIme;
118    private int mImeHeight;
119    private WindowState mDelayedImeWin;
120    private boolean mAdjustedForDivider;
121    private float mDividerAnimationStart;
122    private float mDividerAnimationTarget;
123    private float mLastAnimationProgress;
124    private float mLastDividerProgress;
125
126    DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
127        mService = service;
128        mDisplayContent = displayContent;
129        final Context context = service.mContext;
130        mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId(),
131                "DockedStackDim");
132        mMinimizedDockInterpolator = AnimationUtils.loadInterpolator(
133                context, android.R.interpolator.fast_out_slow_in);
134        loadDimens();
135    }
136
137    private void loadDimens() {
138        final Context context = mService.mContext;
139        mDividerWindowWidth = context.getResources().getDimensionPixelSize(
140                com.android.internal.R.dimen.docked_stack_divider_thickness);
141        mDividerInsets = context.getResources().getDimensionPixelSize(
142                com.android.internal.R.dimen.docked_stack_divider_insets);
143        mDividerWindowWidthInactive = WindowManagerService.dipToPixel(
144                DIVIDER_WIDTH_INACTIVE_DP, mDisplayContent.getDisplayMetrics());
145    }
146
147    void onConfigurationChanged() {
148        loadDimens();
149    }
150
151    boolean isResizing() {
152        return mResizing;
153    }
154
155    int getContentWidth() {
156        return mDividerWindowWidth - 2 * mDividerInsets;
157    }
158
159    int getContentInsets() {
160        return mDividerInsets;
161    }
162
163    int getContentWidthInactive() {
164        return mDividerWindowWidthInactive;
165    }
166
167    void setResizing(boolean resizing) {
168        if (mResizing != resizing) {
169            mResizing = resizing;
170            resetDragResizingChangeReported();
171        }
172    }
173
174    void setTouchRegion(Rect touchRegion) {
175        mTouchRegion.set(touchRegion);
176    }
177
178    void getTouchRegion(Rect outRegion) {
179        outRegion.set(mTouchRegion);
180        outRegion.offset(mWindow.getFrameLw().left, mWindow.getFrameLw().top);
181    }
182
183    private void resetDragResizingChangeReported() {
184        final WindowList windowList = mDisplayContent.getWindowList();
185        for (int i = windowList.size() - 1; i >= 0; i--) {
186            windowList.get(i).resetDragResizingChangeReported();
187        }
188    }
189
190    void setWindow(WindowState window) {
191        mWindow = window;
192        reevaluateVisibility(false);
193    }
194
195    void reevaluateVisibility(boolean force) {
196        if (mWindow == null) {
197            return;
198        }
199        TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID);
200
201        // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide
202        final boolean visible = stack != null;
203        if (mLastVisibility == visible && !force) {
204            return;
205        }
206        mLastVisibility = visible;
207        notifyDockedDividerVisibilityChanged(visible);
208        if (!visible) {
209            setResizeDimLayer(false, INVALID_STACK_ID, 0f);
210        }
211    }
212
213    boolean wasVisible() {
214        return mLastVisibility;
215    }
216
217    void setAdjustedForIme(
218            boolean adjustedForIme, boolean adjustedForDivider,
219            boolean animate, WindowState imeWin, int imeHeight) {
220        if (mAdjustedForIme != adjustedForIme || (adjustedForIme && mImeHeight != imeHeight)
221                || mAdjustedForDivider != adjustedForDivider) {
222            if (animate) {
223                startImeAdjustAnimation(adjustedForIme, adjustedForDivider, imeWin);
224            } else {
225                // Animation might be delayed, so only notify if we don't run an animation.
226                notifyAdjustedForImeChanged(adjustedForIme || adjustedForDivider, 0 /* duration */);
227            }
228            mAdjustedForIme = adjustedForIme;
229            mImeHeight = imeHeight;
230            mAdjustedForDivider = adjustedForDivider;
231        }
232    }
233
234    int getImeHeightAdjustedFor() {
235        return mImeHeight;
236    }
237
238    void positionDockedStackedDivider(Rect frame) {
239        TaskStack stack = mDisplayContent.getDockedStackLocked();
240        if (stack == null) {
241            // Unfortunately we might end up with still having a divider, even though the underlying
242            // stack was already removed. This is because we are on AM thread and the removal of the
243            // divider was deferred to WM thread and hasn't happened yet. In that case let's just
244            // keep putting it in the same place it was before the stack was removed to have
245            // continuity and prevent it from jumping to the center. It will get hidden soon.
246            frame.set(mLastRect);
247            return;
248        } else {
249            stack.getDimBounds(mTmpRect);
250        }
251        int side = stack.getDockSide();
252        switch (side) {
253            case DOCKED_LEFT:
254                frame.set(mTmpRect.right - mDividerInsets, frame.top,
255                        mTmpRect.right + frame.width() - mDividerInsets, frame.bottom);
256                break;
257            case DOCKED_TOP:
258                frame.set(frame.left, mTmpRect.bottom - mDividerInsets,
259                        mTmpRect.right, mTmpRect.bottom + frame.height() - mDividerInsets);
260                break;
261            case DOCKED_RIGHT:
262                frame.set(mTmpRect.left - frame.width() + mDividerInsets, frame.top,
263                        mTmpRect.left + mDividerInsets, frame.bottom);
264                break;
265            case DOCKED_BOTTOM:
266                frame.set(frame.left, mTmpRect.top - frame.height() + mDividerInsets,
267                        frame.right, mTmpRect.top + mDividerInsets);
268                break;
269        }
270        mLastRect.set(frame);
271    }
272
273    void notifyDockedDividerVisibilityChanged(boolean visible) {
274        final int size = mDockedStackListeners.beginBroadcast();
275        for (int i = 0; i < size; ++i) {
276            final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
277            try {
278                listener.onDividerVisibilityChanged(visible);
279            } catch (RemoteException e) {
280                Slog.e(TAG_WM, "Error delivering divider visibility changed event.", e);
281            }
282        }
283        mDockedStackListeners.finishBroadcast();
284    }
285
286    void notifyDockedStackExistsChanged(boolean exists) {
287        final int size = mDockedStackListeners.beginBroadcast();
288        for (int i = 0; i < size; ++i) {
289            final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
290            try {
291                listener.onDockedStackExistsChanged(exists);
292            } catch (RemoteException e) {
293                Slog.e(TAG_WM, "Error delivering docked stack exists changed event.", e);
294            }
295        }
296        mDockedStackListeners.finishBroadcast();
297        if (!exists) {
298            setMinimizedDockedStack(false);
299        }
300    }
301
302    void notifyDockedStackMinimizedChanged(boolean minimizedDock, long animDuration) {
303        mService.mH.removeMessages(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED);
304        mService.mH.obtainMessage(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED,
305                minimizedDock ? 1 : 0, 0).sendToTarget();
306        final int size = mDockedStackListeners.beginBroadcast();
307        for (int i = 0; i < size; ++i) {
308            final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
309            try {
310                listener.onDockedStackMinimizedChanged(minimizedDock, animDuration);
311            } catch (RemoteException e) {
312                Slog.e(TAG_WM, "Error delivering minimized dock changed event.", e);
313            }
314        }
315        mDockedStackListeners.finishBroadcast();
316    }
317
318    void notifyDockSideChanged(int newDockSide) {
319        final int size = mDockedStackListeners.beginBroadcast();
320        for (int i = 0; i < size; ++i) {
321            final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
322            try {
323                listener.onDockSideChanged(newDockSide);
324            } catch (RemoteException e) {
325                Slog.e(TAG_WM, "Error delivering dock side changed event.", e);
326            }
327        }
328        mDockedStackListeners.finishBroadcast();
329    }
330
331    void notifyAdjustedForImeChanged(boolean adjustedForIme, long animDuration) {
332        final int size = mDockedStackListeners.beginBroadcast();
333        for (int i = 0; i < size; ++i) {
334            final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
335            try {
336                listener.onAdjustedForImeChanged(adjustedForIme, animDuration);
337            } catch (RemoteException e) {
338                Slog.e(TAG_WM, "Error delivering adjusted for ime changed event.", e);
339            }
340        }
341        mDockedStackListeners.finishBroadcast();
342    }
343
344    void registerDockedStackListener(IDockedStackListener listener) {
345        mDockedStackListeners.register(listener);
346        notifyDockedDividerVisibilityChanged(wasVisible());
347        notifyDockedStackExistsChanged(
348                mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID) != null);
349        notifyDockedStackMinimizedChanged(mMinimizedDock, 0 /* animDuration */);
350        notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
351
352    }
353
354    void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
355        SurfaceControl.openTransaction();
356        final TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(targetStackId);
357        final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
358        boolean visibleAndValid = visible && stack != null && dockedStack != null;
359        if (visibleAndValid) {
360            stack.getDimBounds(mTmpRect);
361            if (mTmpRect.height() > 0 && mTmpRect.width() > 0) {
362                mDimLayer.setBounds(mTmpRect);
363                mDimLayer.show(mService.mLayersController.getResizeDimLayer(),
364                        alpha, 0 /* duration */);
365            } else {
366                visibleAndValid = false;
367            }
368        }
369        if (!visibleAndValid) {
370            mDimLayer.hide();
371        }
372        SurfaceControl.closeTransaction();
373    }
374
375    /**
376     * Notifies the docked stack divider controller of a visibility change that happens without
377     * an animation.
378     */
379    void notifyAppVisibilityChanged() {
380        checkMinimizeChanged(false /* animate */);
381    }
382
383    void notifyAppTransitionStarting() {
384        checkMinimizeChanged(true /* animate */);
385    }
386
387    boolean isMinimizedDock() {
388        return mMinimizedDock;
389    }
390
391    private void checkMinimizeChanged(boolean animate) {
392        if (mDisplayContent.getDockedStackVisibleForUserLocked() == null) {
393            return;
394        }
395        final TaskStack homeStack = mDisplayContent.getHomeStack();
396        if (homeStack == null) {
397            return;
398        }
399        final Task homeTask = homeStack.findHomeTask();
400        if (homeTask == null || !isWithinDisplay(homeTask)) {
401            return;
402        }
403        final TaskStack fullscreenStack
404                = mService.mStackIdToStack.get(FULLSCREEN_WORKSPACE_STACK_ID);
405        final ArrayList<Task> homeStackTasks = homeStack.getTasks();
406        final Task topHomeStackTask = homeStackTasks.get(homeStackTasks.size() - 1);
407        final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
408        final boolean homeBehind = (fullscreenStack != null && fullscreenStack.isVisibleLocked())
409                || (homeStackTasks.size() > 1 && topHomeStackTask != homeTask);
410        setMinimizedDockedStack(homeVisible && !homeBehind, animate);
411    }
412
413    private boolean isWithinDisplay(Task task) {
414        task.mStack.getBounds(mTmpRect);
415        mDisplayContent.getLogicalDisplayRect(mTmpRect2);
416        return mTmpRect.intersect(mTmpRect2);
417    }
418
419    /**
420     * Sets whether the docked stack is currently in a minimized state, i.e. all the tasks in the
421     * docked stack are heavily clipped so you can only see a minimal peek state.
422     *
423     * @param minimizedDock Whether the docked stack is currently minimized.
424     * @param animate Whether to animate the change.
425     */
426    private void setMinimizedDockedStack(boolean minimizedDock, boolean animate) {
427        final boolean wasMinimized = mMinimizedDock;
428        mMinimizedDock = minimizedDock;
429        if (minimizedDock == wasMinimized) {
430            return;
431        }
432
433        clearImeAdjustAnimation();
434        if (minimizedDock) {
435            if (animate) {
436                startAdjustAnimation(0f, 1f);
437            } else {
438                setMinimizedDockedStack(true);
439            }
440        } else {
441            if (animate) {
442                startAdjustAnimation(1f, 0f);
443            } else {
444                setMinimizedDockedStack(false);
445            }
446        }
447    }
448
449    private void clearImeAdjustAnimation() {
450        final ArrayList<TaskStack> stacks = mDisplayContent.getStacks();
451        for (int i = stacks.size() - 1; i >= 0; --i) {
452            final TaskStack stack = stacks.get(i);
453            if (stack != null && stack.isAdjustedForIme()) {
454                stack.resetAdjustedForIme(true /* adjustBoundsNow */);
455            }
456        }
457        mAnimatingForIme = false;
458    }
459
460    private void startAdjustAnimation(float from, float to) {
461        mAnimatingForMinimizedDockedStack = true;
462        mAnimationStarted = false;
463        mAnimationStart = from;
464        mAnimationTarget = to;
465    }
466
467    private void startImeAdjustAnimation(
468            boolean adjustedForIme, boolean adjustedForDivider, WindowState imeWin) {
469        mAnimatingForIme = true;
470        mAnimationStarted = false;
471
472        // If we're not in an animation, the starting point depends on whether we're adjusted
473        // or not. If we're already in an animation, we start from where the current animation
474        // left off, so that the motion doesn't look discontinuous.
475        if (!mAnimatingForIme) {
476            mAnimationStart = mAdjustedForIme ? 1 : 0;
477            mDividerAnimationStart = mAdjustedForDivider ? 1 : 0;
478            mLastAnimationProgress = mAnimationStart;
479            mLastDividerProgress = mDividerAnimationStart;
480        } else {
481            mAnimationStart = mLastAnimationProgress;
482            mDividerAnimationStart = mLastDividerProgress;
483        }
484        mAnimationTarget = adjustedForIme ? 1 : 0;
485        mDividerAnimationTarget = adjustedForDivider ? 1 : 0;
486
487        final ArrayList<TaskStack> stacks = mDisplayContent.getStacks();
488        for (int i = stacks.size() - 1; i >= 0; --i) {
489            final TaskStack stack = stacks.get(i);
490            if (stack.isVisibleLocked() && stack.isAdjustedForIme()) {
491                stack.beginImeAdjustAnimation();
492            }
493        }
494
495        // We put all tasks into drag resizing mode - wait until all of them have completed the
496        // drag resizing switch.
497        if (!mService.mWaitingForDrawn.isEmpty()) {
498            mService.mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT);
499            mService.mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT,
500                    IME_ADJUST_DRAWN_TIMEOUT);
501            mAnimationStartDelayed = true;
502            if (imeWin != null) {
503
504                // There might be an old window delaying the animation start - clear it.
505                if (mDelayedImeWin != null) {
506                    mDelayedImeWin.mWinAnimator.endDelayingAnimationStart();
507                }
508                mDelayedImeWin = imeWin;
509                imeWin.mWinAnimator.startDelayingAnimationStart();
510            }
511            mService.mWaitingForDrawnCallback = () -> {
512                mAnimationStartDelayed = false;
513                if (mDelayedImeWin != null) {
514                    mDelayedImeWin.mWinAnimator.endDelayingAnimationStart();
515                }
516                notifyAdjustedForImeChanged(
517                        adjustedForIme || adjustedForDivider, IME_ADJUST_ANIM_DURATION);
518            };
519        } else {
520            notifyAdjustedForImeChanged(
521                    adjustedForIme || adjustedForDivider, IME_ADJUST_ANIM_DURATION);
522        }
523    }
524
525    private void setMinimizedDockedStack(boolean minimized) {
526        final TaskStack stack = mDisplayContent.getDockedStackVisibleForUserLocked();
527        notifyDockedStackMinimizedChanged(minimized, 0);
528        if (stack == null) {
529            return;
530        }
531        if (stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f)) {
532            mService.mWindowPlacerLocked.performSurfacePlacement();
533        }
534    }
535
536    private boolean isAnimationMaximizing() {
537        return mAnimationTarget == 0f;
538    }
539
540    public boolean animate(long now) {
541        if (mWindow == null) {
542            return false;
543        }
544        if (mAnimatingForMinimizedDockedStack) {
545            return animateForMinimizedDockedStack(now);
546        } else if (mAnimatingForIme) {
547            return animateForIme(now);
548        } else {
549            if (mDimLayer != null && mDimLayer.isDimming()) {
550                mDimLayer.setLayer(mService.mLayersController.getResizeDimLayer());
551            }
552            return false;
553        }
554    }
555
556    private boolean animateForIme(long now) {
557        if (!mAnimationStarted || mAnimationStartDelayed) {
558            mAnimationStarted = true;
559            mAnimationStartTime = now;
560            mAnimationDuration = (long)
561                    (IME_ADJUST_ANIM_DURATION * mService.getWindowAnimationScaleLocked());
562        }
563        float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration);
564        t = (mAnimationTarget == 1f ? IME_ADJUST_ENTRY_INTERPOLATOR : TOUCH_RESPONSE_INTERPOLATOR)
565                .getInterpolation(t);
566        final ArrayList<TaskStack> stacks = mDisplayContent.getStacks();
567        boolean updated = false;
568        for (int i = stacks.size() - 1; i >= 0; --i) {
569            final TaskStack stack = stacks.get(i);
570            if (stack != null && stack.isAdjustedForIme()) {
571                if (t >= 1f && mAnimationTarget == 0f && mDividerAnimationTarget == 0f) {
572                    stack.resetAdjustedForIme(true /* adjustBoundsNow */);
573                    updated = true;
574                } else {
575                    mLastAnimationProgress = getInterpolatedAnimationValue(t);
576                    mLastDividerProgress = getInterpolatedDividerValue(t);
577                    updated |= stack.updateAdjustForIme(
578                            mLastAnimationProgress,
579                            mLastDividerProgress,
580                            false /* force */);
581                }
582                if (t >= 1f) {
583                    stack.endImeAdjustAnimation();
584                }
585            }
586        }
587        if (updated) {
588            mService.mWindowPlacerLocked.performSurfacePlacement();
589        }
590        if (t >= 1.0f) {
591            mLastAnimationProgress = mAnimationTarget;
592            mLastDividerProgress = mDividerAnimationTarget;
593            mAnimatingForIme = false;
594            return false;
595        } else {
596            return true;
597        }
598    }
599
600    private boolean animateForMinimizedDockedStack(long now) {
601        final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
602        if (!mAnimationStarted) {
603            mAnimationStarted = true;
604            mAnimationStartTime = now;
605            final long transitionDuration = isAnimationMaximizing()
606                    ? mService.mAppTransition.getLastClipRevealTransitionDuration()
607                    : DEFAULT_APP_TRANSITION_DURATION;
608            mAnimationDuration = (long)
609                    (transitionDuration * mService.getTransitionAnimationScaleLocked());
610            mMaximizeMeetFraction = getClipRevealMeetFraction(stack);
611            notifyDockedStackMinimizedChanged(mMinimizedDock,
612                    (long) (mAnimationDuration * mMaximizeMeetFraction));
613        }
614        float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration);
615        t = (isAnimationMaximizing() ? TOUCH_RESPONSE_INTERPOLATOR : mMinimizedDockInterpolator)
616                .getInterpolation(t);
617        if (stack != null) {
618            if (stack.setAdjustedForMinimizedDock(getMinimizeAmount(stack, t))) {
619                mService.mWindowPlacerLocked.performSurfacePlacement();
620            }
621        }
622        if (t >= 1.0f) {
623            mAnimatingForMinimizedDockedStack = false;
624            return false;
625        } else {
626            return true;
627        }
628    }
629
630    private float getInterpolatedAnimationValue(float t) {
631        return t * mAnimationTarget + (1 - t) * mAnimationStart;
632    }
633
634    private float getInterpolatedDividerValue(float t) {
635        return t * mDividerAnimationTarget + (1 - t) * mDividerAnimationStart;
636    }
637
638    /**
639     * Gets the amount how much to minimize a stack depending on the interpolated fraction t.
640     */
641    private float getMinimizeAmount(TaskStack stack, float t) {
642        final float naturalAmount = getInterpolatedAnimationValue(t);
643        if (isAnimationMaximizing()) {
644            return adjustMaximizeAmount(stack, t, naturalAmount);
645        } else {
646            return naturalAmount;
647        }
648    }
649
650    /**
651     * When maximizing the stack during a clip reveal transition, this adjusts the minimize amount
652     * during the transition such that the edge of the clip reveal rect is met earlier in the
653     * transition so we don't create a visible "hole", but only if both the clip reveal and the
654     * docked stack divider start from about the same portion on the screen.
655     */
656    private float adjustMaximizeAmount(TaskStack stack, float t, float naturalAmount) {
657        if (mMaximizeMeetFraction == 1f) {
658            return naturalAmount;
659        }
660        final int minimizeDistance = stack.getMinimizeDistance();
661        float startPrime = mService.mAppTransition.getLastClipRevealMaxTranslation()
662                / (float) minimizeDistance;
663        final float amountPrime = t * mAnimationTarget + (1 - t) * startPrime;
664        final float t2 = Math.min(t / mMaximizeMeetFraction, 1);
665        return amountPrime * t2 + naturalAmount * (1 - t2);
666    }
667
668    /**
669     * Retrieves the animation fraction at which the docked stack has to meet the clip reveal
670     * edge. See {@link #adjustMaximizeAmount}.
671     */
672    private float getClipRevealMeetFraction(TaskStack stack) {
673        if (!isAnimationMaximizing() || stack == null ||
674                !mService.mAppTransition.hadClipRevealAnimation()) {
675            return 1f;
676        }
677        final int minimizeDistance = stack.getMinimizeDistance();
678        final float fraction = Math.abs(mService.mAppTransition.getLastClipRevealMaxTranslation())
679                / (float) minimizeDistance;
680        final float t = Math.max(0, Math.min(1, (fraction - CLIP_REVEAL_MEET_FRACTION_MIN)
681                / (CLIP_REVEAL_MEET_FRACTION_MAX - CLIP_REVEAL_MEET_FRACTION_MIN)));
682        return CLIP_REVEAL_MEET_EARLIEST
683                + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST);
684    }
685
686    @Override
687    public boolean isFullscreen() {
688        return false;
689    }
690
691    @Override
692    public DisplayInfo getDisplayInfo() {
693        return mDisplayContent.getDisplayInfo();
694    }
695
696    @Override
697    public void getDimBounds(Rect outBounds) {
698        // This dim layer user doesn't need this.
699    }
700
701    @Override
702    public String toShortString() {
703        return TAG;
704    }
705
706    WindowState getWindow() {
707        return mWindow;
708    }
709
710    void dump(String prefix, PrintWriter pw) {
711        pw.println(prefix + "DockedStackDividerController");
712        pw.println(prefix + "  mLastVisibility=" + mLastVisibility);
713        pw.println(prefix + "  mMinimizedDock=" + mMinimizedDock);
714        pw.println(prefix + "  mAdjustedForIme=" + mAdjustedForIme);
715        pw.println(prefix + "  mAdjustedForDivider=" + mAdjustedForDivider);
716        if (mDimLayer.isDimming()) {
717            pw.println(prefix + "  Dim layer is dimming: ");
718            mDimLayer.printTo(prefix + "    ", pw);
719        }
720    }
721}
722