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