[go: nahoru, domu]

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