/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wm; import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.WindowManager.DOCKED_BOTTOM; import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; import static android.view.WindowManager.DOCKED_TOP; import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION; import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR; import static com.android.server.wm.AppTransition.TRANSIT_NONE; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED; import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM; import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.ArraySet; import android.util.Slog; import android.view.DisplayInfo; import android.view.IDockedStackListener; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import android.view.inputmethod.InputMethodManagerInternal; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.internal.policy.DockedDividerUtils; import com.android.server.LocalServices; import com.android.server.wm.DimLayer.DimLayerUser; import com.android.server.wm.WindowManagerService.H; import java.io.PrintWriter; /** * Keeps information about the docked stack divider. */ public class DockedStackDividerController implements DimLayerUser { private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM; /** * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip * revealing surface at the earliest. */ private static final float CLIP_REVEAL_MEET_EARLIEST = 0.6f; /** * The fraction during the maximize/clip reveal animation the divider meets the edge of the clip * revealing surface at the latest. */ private static final float CLIP_REVEAL_MEET_LAST = 1f; /** * If the app translates at least CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance, we start * meet somewhere between {@link #CLIP_REVEAL_MEET_LAST} and {@link #CLIP_REVEAL_MEET_EARLIEST}. */ private static final float CLIP_REVEAL_MEET_FRACTION_MIN = 0.4f; /** * If the app translates equals or more than CLIP_REVEAL_MEET_FRACTION_MIN * minimize distance, * we meet at {@link #CLIP_REVEAL_MEET_EARLIEST}. */ private static final float CLIP_REVEAL_MEET_FRACTION_MAX = 0.8f; private static final Interpolator IME_ADJUST_ENTRY_INTERPOLATOR = new PathInterpolator(0.2f, 0f, 0.1f, 1f); private static final long IME_ADJUST_ANIM_DURATION = 280; private static final long IME_ADJUST_DRAWN_TIMEOUT = 200; private static final int DIVIDER_WIDTH_INACTIVE_DP = 4; private final WindowManagerService mService; private final DisplayContent mDisplayContent; private int mDividerWindowWidth; private int mDividerWindowWidthInactive; private int mDividerInsets; private int mTaskHeightInMinimizedMode; private boolean mResizing; private WindowState mWindow; private final Rect mTmpRect = new Rect(); private final Rect mTmpRect2 = new Rect(); private final Rect mTmpRect3 = new Rect(); private final Rect mLastRect = new Rect(); private boolean mLastVisibility = false; private final RemoteCallbackList mDockedStackListeners = new RemoteCallbackList<>(); private final DimLayer mDimLayer; private boolean mMinimizedDock; private boolean mAnimatingForMinimizedDockedStack; private boolean mAnimationStarted; private long mAnimationStartTime; private float mAnimationStart; private float mAnimationTarget; private long mAnimationDuration; private boolean mAnimationStartDelayed; private final Interpolator mMinimizedDockInterpolator; private float mMaximizeMeetFraction; private final Rect mTouchRegion = new Rect(); private boolean mAnimatingForIme; private boolean mAdjustedForIme; private int mImeHeight; private WindowState mDelayedImeWin; private boolean mAdjustedForDivider; private float mDividerAnimationStart; private float mDividerAnimationTarget; float mLastAnimationProgress; float mLastDividerProgress; private final DividerSnapAlgorithm[] mSnapAlgorithmForRotation = new DividerSnapAlgorithm[4]; private boolean mImeHideRequested; DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) { mService = service; mDisplayContent = displayContent; final Context context = service.mContext; mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId(), "DockedStackDim"); mMinimizedDockInterpolator = AnimationUtils.loadInterpolator( context, android.R.interpolator.fast_out_slow_in); loadDimens(); } int getSmallestWidthDpForBounds(Rect bounds) { final DisplayInfo di = mDisplayContent.getDisplayInfo(); final int baseDisplayWidth = mDisplayContent.mBaseDisplayWidth; final int baseDisplayHeight = mDisplayContent.mBaseDisplayHeight; int minWidth = Integer.MAX_VALUE; // Go through all screen orientations and find the orientation in which the task has the // smallest width. for (int rotation = 0; rotation < 4; rotation++) { mTmpRect.set(bounds); mDisplayContent.rotateBounds(di.rotation, rotation, mTmpRect); final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); mTmpRect2.set(0, 0, rotated ? baseDisplayHeight : baseDisplayWidth, rotated ? baseDisplayWidth : baseDisplayHeight); final int orientation = mTmpRect2.width() <= mTmpRect2.height() ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; final int dockSide = TaskStack.getDockSideUnchecked(mTmpRect, mTmpRect2, orientation); final int position = DockedDividerUtils.calculatePositionForBounds(mTmpRect, dockSide, getContentWidth()); // Since we only care about feasible states, snap to the closest snap target, like it // would happen when actually rotating the screen. final int snappedPosition = mSnapAlgorithmForRotation[rotation] .calculateNonDismissingSnapTarget(position).position; DockedDividerUtils.calculateBoundsForPosition(snappedPosition, dockSide, mTmpRect, mTmpRect2.width(), mTmpRect2.height(), getContentWidth()); mService.mPolicy.getStableInsetsLw(rotation, mTmpRect2.width(), mTmpRect2.height(), mTmpRect3); mService.intersectDisplayInsetBounds(mTmpRect2, mTmpRect3, mTmpRect); minWidth = Math.min(mTmpRect.width(), minWidth); } return (int) (minWidth / mDisplayContent.getDisplayMetrics().density); } void getHomeStackBoundsInDockedMode(Rect outBounds) { final DisplayInfo di = mDisplayContent.getDisplayInfo(); mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, mTmpRect); int dividerSize = mDividerWindowWidth - 2 * mDividerInsets; Configuration configuration = mDisplayContent.getConfiguration(); // The offset in the left (landscape)/top (portrait) is calculated with the minimized // offset value with the divider size and any system insets in that direction. if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { outBounds.set(0, mTaskHeightInMinimizedMode + dividerSize + mTmpRect.top, di.logicalWidth, di.logicalHeight); } else { // In landscape append the left position with the statusbar height to match the // minimized size height in portrait mode. outBounds.set(mTaskHeightInMinimizedMode + dividerSize + mTmpRect.left + mTmpRect.top, 0, di.logicalWidth, di.logicalHeight); } } boolean isHomeStackResizable() { final TaskStack homeStack = mDisplayContent.getHomeStack(); if (homeStack == null) { return false; } final Task homeTask = homeStack.findHomeTask(); return homeTask != null && homeTask.isResizeable(); } private void initSnapAlgorithmForRotations() { final Configuration baseConfig = mDisplayContent.getConfiguration(); // Initialize the snap algorithms for all 4 screen orientations. final Configuration config = new Configuration(); for (int rotation = 0; rotation < 4; rotation++) { final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); final int dw = rotated ? mDisplayContent.mBaseDisplayHeight : mDisplayContent.mBaseDisplayWidth; final int dh = rotated ? mDisplayContent.mBaseDisplayWidth : mDisplayContent.mBaseDisplayHeight; mService.mPolicy.getStableInsetsLw(rotation, dw, dh, mTmpRect); config.unset(); config.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; final int displayId = mDisplayContent.getDisplayId(); final int appWidth = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, baseConfig.uiMode, displayId); final int appHeight = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, rotation, baseConfig.uiMode, displayId); mService.mPolicy.getNonDecorInsetsLw(rotation, dw, dh, mTmpRect); final int leftInset = mTmpRect.left; final int topInset = mTmpRect.top; config.setAppBounds(leftInset /*left*/, topInset /*top*/, leftInset + appWidth /*right*/, topInset + appHeight /*bottom*/); config.screenWidthDp = (int) (mService.mPolicy.getConfigDisplayWidth(dw, dh, rotation, baseConfig.uiMode, displayId) / mDisplayContent.getDisplayMetrics().density); config.screenHeightDp = (int) (mService.mPolicy.getConfigDisplayHeight(dw, dh, rotation, baseConfig.uiMode, displayId) / mDisplayContent.getDisplayMetrics().density); final Context rotationContext = mService.mContext.createConfigurationContext(config); mSnapAlgorithmForRotation[rotation] = new DividerSnapAlgorithm( rotationContext.getResources(), dw, dh, getContentWidth(), config.orientation == ORIENTATION_PORTRAIT, mTmpRect); } } private void loadDimens() { final Context context = mService.mContext; mDividerWindowWidth = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.docked_stack_divider_thickness); mDividerInsets = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.docked_stack_divider_insets); mDividerWindowWidthInactive = WindowManagerService.dipToPixel( DIVIDER_WIDTH_INACTIVE_DP, mDisplayContent.getDisplayMetrics()); mTaskHeightInMinimizedMode = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.task_height_of_minimized_mode); initSnapAlgorithmForRotations(); } void onConfigurationChanged() { loadDimens(); } boolean isResizing() { return mResizing; } int getContentWidth() { return mDividerWindowWidth - 2 * mDividerInsets; } int getContentInsets() { return mDividerInsets; } int getContentWidthInactive() { return mDividerWindowWidthInactive; } void setResizing(boolean resizing) { if (mResizing != resizing) { mResizing = resizing; resetDragResizingChangeReported(); } } void setTouchRegion(Rect touchRegion) { mTouchRegion.set(touchRegion); } void getTouchRegion(Rect outRegion) { outRegion.set(mTouchRegion); outRegion.offset(mWindow.getFrameLw().left, mWindow.getFrameLw().top); } private void resetDragResizingChangeReported() { mDisplayContent.forAllWindows(WindowState::resetDragResizingChangeReported, true /* traverseTopToBottom */ ); } void setWindow(WindowState window) { mWindow = window; reevaluateVisibility(false); } void reevaluateVisibility(boolean force) { if (mWindow == null) { return; } TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility(); // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide final boolean visible = stack != null; if (mLastVisibility == visible && !force) { return; } mLastVisibility = visible; notifyDockedDividerVisibilityChanged(visible); if (!visible) { setResizeDimLayer(false, INVALID_STACK_ID, 0f); } } private boolean wasVisible() { return mLastVisibility; } void setAdjustedForIme( boolean adjustedForIme, boolean adjustedForDivider, boolean animate, WindowState imeWin, int imeHeight) { if (mAdjustedForIme != adjustedForIme || (adjustedForIme && mImeHeight != imeHeight) || mAdjustedForDivider != adjustedForDivider) { if (animate && !mAnimatingForMinimizedDockedStack) { startImeAdjustAnimation(adjustedForIme, adjustedForDivider, imeWin); } else { // Animation might be delayed, so only notify if we don't run an animation. notifyAdjustedForImeChanged(adjustedForIme || adjustedForDivider, 0 /* duration */); } mAdjustedForIme = adjustedForIme; mImeHeight = imeHeight; mAdjustedForDivider = adjustedForDivider; } } int getImeHeightAdjustedFor() { return mImeHeight; } void positionDockedStackedDivider(Rect frame) { TaskStack stack = mDisplayContent.getDockedStackLocked(); if (stack == null) { // Unfortunately we might end up with still having a divider, even though the underlying // stack was already removed. This is because we are on AM thread and the removal of the // divider was deferred to WM thread and hasn't happened yet. In that case let's just // keep putting it in the same place it was before the stack was removed to have // continuity and prevent it from jumping to the center. It will get hidden soon. frame.set(mLastRect); return; } else { stack.getDimBounds(mTmpRect); } int side = stack.getDockSide(); switch (side) { case DOCKED_LEFT: frame.set(mTmpRect.right - mDividerInsets, frame.top, mTmpRect.right + frame.width() - mDividerInsets, frame.bottom); break; case DOCKED_TOP: frame.set(frame.left, mTmpRect.bottom - mDividerInsets, mTmpRect.right, mTmpRect.bottom + frame.height() - mDividerInsets); break; case DOCKED_RIGHT: frame.set(mTmpRect.left - frame.width() + mDividerInsets, frame.top, mTmpRect.left + mDividerInsets, frame.bottom); break; case DOCKED_BOTTOM: frame.set(frame.left, mTmpRect.top - frame.height() + mDividerInsets, frame.right, mTmpRect.top + mDividerInsets); break; } mLastRect.set(frame); } private void notifyDockedDividerVisibilityChanged(boolean visible) { final int size = mDockedStackListeners.beginBroadcast(); for (int i = 0; i < size; ++i) { final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); try { listener.onDividerVisibilityChanged(visible); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering divider visibility changed event.", e); } } mDockedStackListeners.finishBroadcast(); } void notifyDockedStackExistsChanged(boolean exists) { // TODO(multi-display): Perform all actions only for current display. final int size = mDockedStackListeners.beginBroadcast(); for (int i = 0; i < size; ++i) { final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); try { listener.onDockedStackExistsChanged(exists); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering docked stack exists changed event.", e); } } mDockedStackListeners.finishBroadcast(); if (exists) { InputMethodManagerInternal inputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class); if (inputMethodManagerInternal != null) { // Hide the current IME to avoid problems with animations from IME adjustment when // attaching the docked stack. inputMethodManagerInternal.hideCurrentInputMethod(); mImeHideRequested = true; } return; } setMinimizedDockedStack(false /* minimizedDock */, false /* animate */); } /** * Resets the state that IME hide has been requested. See {@link #isImeHideRequested}. */ void resetImeHideRequested() { mImeHideRequested = false; } /** * The docked stack divider controller makes sure the IME gets hidden when attaching the docked * stack, to avoid animation problems. This flag indicates whether the request to hide the IME * has been sent in an asynchronous manner, and the IME should be treated as hidden already. * * @return whether IME hide request has been sent */ boolean isImeHideRequested() { return mImeHideRequested; } private void notifyDockedStackMinimizedChanged(boolean minimizedDock, boolean animate, boolean isHomeStackResizable) { long animDuration = 0; if (animate) { final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID); final long transitionDuration = isAnimationMaximizing() ? mService.mAppTransition.getLastClipRevealTransitionDuration() : DEFAULT_APP_TRANSITION_DURATION; mAnimationDuration = (long) (transitionDuration * mService.getTransitionAnimationScaleLocked()); mMaximizeMeetFraction = getClipRevealMeetFraction(stack); animDuration = (long) (mAnimationDuration * mMaximizeMeetFraction); } mService.mH.removeMessages(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED); mService.mH.obtainMessage(NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED, minimizedDock ? 1 : 0, 0).sendToTarget(); final int size = mDockedStackListeners.beginBroadcast(); for (int i = 0; i < size; ++i) { final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); try { listener.onDockedStackMinimizedChanged(minimizedDock, animDuration, isHomeStackResizable); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering minimized dock changed event.", e); } } mDockedStackListeners.finishBroadcast(); } void notifyDockSideChanged(int newDockSide) { final int size = mDockedStackListeners.beginBroadcast(); for (int i = 0; i < size; ++i) { final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); try { listener.onDockSideChanged(newDockSide); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering dock side changed event.", e); } } mDockedStackListeners.finishBroadcast(); } private void notifyAdjustedForImeChanged(boolean adjustedForIme, long animDuration) { final int size = mDockedStackListeners.beginBroadcast(); for (int i = 0; i < size; ++i) { final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i); try { listener.onAdjustedForImeChanged(adjustedForIme, animDuration); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering adjusted for ime changed event.", e); } } mDockedStackListeners.finishBroadcast(); } void registerDockedStackListener(IDockedStackListener listener) { mDockedStackListeners.register(listener); notifyDockedDividerVisibilityChanged(wasVisible()); notifyDockedStackExistsChanged(mDisplayContent.getDockedStackIgnoringVisibility() != null); notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */, isHomeStackResizable()); notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */); } void setResizeDimLayer(boolean visible, int targetStackId, float alpha) { mService.openSurfaceTransaction(); final TaskStack stack = mDisplayContent.getStackById(targetStackId); final TaskStack dockedStack = mDisplayContent.getDockedStackLocked(); boolean visibleAndValid = visible && stack != null && dockedStack != null; if (visibleAndValid) { stack.getDimBounds(mTmpRect); if (mTmpRect.height() > 0 && mTmpRect.width() > 0) { mDimLayer.setBounds(mTmpRect); mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */); } else { visibleAndValid = false; } } if (!visibleAndValid) { mDimLayer.hide(); } mService.closeSurfaceTransaction(); } /** * @return The layer used for dimming the apps when dismissing docked/fullscreen stack. Just * above all application surfaces. */ private int getResizeDimLayer() { return (mWindow != null) ? mWindow.mLayer - 1 : LAYER_OFFSET_DIM; } /** * Notifies the docked stack divider controller of a visibility change that happens without * an animation. */ void notifyAppVisibilityChanged() { checkMinimizeChanged(false /* animate */); } void notifyAppTransitionStarting(ArraySet openingApps, int appTransition) { final boolean wasMinimized = mMinimizedDock; checkMinimizeChanged(true /* animate */); // We were minimized, and now we are still minimized, but somebody is trying to launch an // app in docked stack, better show recent apps so we actually get unminimized! However do // not do this if keyguard is dismissed such as when the device is unlocking. This catches // any case that was missed in ActivityStarter.postStartActivityUncheckedProcessing because // we couldn't retrace the launch of the app in the docked stack to the launch from // homescreen. if (wasMinimized && mMinimizedDock && containsAppInDockedStack(openingApps) && appTransition != TRANSIT_NONE && !AppTransition.isKeyguardGoingAwayTransit(appTransition)) { mService.showRecentApps(true /* fromHome */); } } /** * @return true if {@param apps} contains an activity in the docked stack, false otherwise. */ private boolean containsAppInDockedStack(ArraySet apps) { for (int i = apps.size() - 1; i >= 0; i--) { final AppWindowToken token = apps.valueAt(i); if (token.getTask() != null && token.getTask().mStack.mStackId == DOCKED_STACK_ID) { return true; } } return false; } boolean isMinimizedDock() { return mMinimizedDock; } private void checkMinimizeChanged(boolean animate) { if (mDisplayContent.getDockedStackIgnoringVisibility() == null) { return; } final TaskStack homeStack = mDisplayContent.getHomeStack(); if (homeStack == null) { return; } final Task homeTask = homeStack.findHomeTask(); if (homeTask == null || !isWithinDisplay(homeTask)) { return; } // Do not minimize when dock is already minimized while keyguard is showing and not // occluded such as unlocking the screen if (mMinimizedDock && mService.mPolicy.isKeyguardShowingAndNotOccluded()) { return; } final TaskStack fullscreenStack = mDisplayContent.getStackById(FULLSCREEN_WORKSPACE_STACK_ID); final boolean homeVisible = homeTask.getTopVisibleAppToken() != null; final boolean homeBehind = (fullscreenStack != null && fullscreenStack.isVisible()) || (homeStack.hasMultipleTaskWithHomeTaskNotTop()); setMinimizedDockedStack(homeVisible && !homeBehind, animate); } private boolean isWithinDisplay(Task task) { task.mStack.getBounds(mTmpRect); mDisplayContent.getLogicalDisplayRect(mTmpRect2); return mTmpRect.intersect(mTmpRect2); } /** * Sets whether the docked stack is currently in a minimized state, i.e. all the tasks in the * docked stack are heavily clipped so you can only see a minimal peek state. * * @param minimizedDock Whether the docked stack is currently minimized. * @param animate Whether to animate the change. */ private void setMinimizedDockedStack(boolean minimizedDock, boolean animate) { final boolean wasMinimized = mMinimizedDock; mMinimizedDock = minimizedDock; if (minimizedDock == wasMinimized) { return; } final boolean imeChanged = clearImeAdjustAnimation(); boolean minimizedChange = false; if (isHomeStackResizable()) { notifyDockedStackMinimizedChanged(minimizedDock, true /* animate */, true /* isHomeStackResizable */); minimizedChange = true; } else { if (minimizedDock) { if (animate) { startAdjustAnimation(0f, 1f); } else { minimizedChange |= setMinimizedDockedStack(true); } } else { if (animate) { startAdjustAnimation(1f, 0f); } else { minimizedChange |= setMinimizedDockedStack(false); } } } if (imeChanged || minimizedChange) { if (imeChanged && !minimizedChange) { Slog.d(TAG, "setMinimizedDockedStack: IME adjust changed due to minimizing," + " minimizedDock=" + minimizedDock + " minimizedChange=" + minimizedChange); } mService.mWindowPlacerLocked.performSurfacePlacement(); } } private boolean clearImeAdjustAnimation() { final boolean changed = mDisplayContent.clearImeAdjustAnimation(); mAnimatingForIme = false; return changed; } private void startAdjustAnimation(float from, float to) { mAnimatingForMinimizedDockedStack = true; mAnimationStarted = false; mAnimationStart = from; mAnimationTarget = to; } private void startImeAdjustAnimation( boolean adjustedForIme, boolean adjustedForDivider, WindowState imeWin) { // If we're not in an animation, the starting point depends on whether we're adjusted // or not. If we're already in an animation, we start from where the current animation // left off, so that the motion doesn't look discontinuous. if (!mAnimatingForIme) { mAnimationStart = mAdjustedForIme ? 1 : 0; mDividerAnimationStart = mAdjustedForDivider ? 1 : 0; mLastAnimationProgress = mAnimationStart; mLastDividerProgress = mDividerAnimationStart; } else { mAnimationStart = mLastAnimationProgress; mDividerAnimationStart = mLastDividerProgress; } mAnimatingForIme = true; mAnimationStarted = false; mAnimationTarget = adjustedForIme ? 1 : 0; mDividerAnimationTarget = adjustedForDivider ? 1 : 0; mDisplayContent.beginImeAdjustAnimation(); // We put all tasks into drag resizing mode - wait until all of them have completed the // drag resizing switch. if (!mService.mWaitingForDrawn.isEmpty()) { mService.mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT); mService.mH.sendEmptyMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, IME_ADJUST_DRAWN_TIMEOUT); mAnimationStartDelayed = true; if (imeWin != null) { // There might be an old window delaying the animation start - clear it. if (mDelayedImeWin != null) { mDelayedImeWin.mWinAnimator.endDelayingAnimationStart(); } mDelayedImeWin = imeWin; imeWin.mWinAnimator.startDelayingAnimationStart(); } // If we are already waiting for something to be drawn, clear out the old one so it // still gets executed. // TODO: Have a real system where we can wait on different windows to be drawn with // different callbacks. if (mService.mWaitingForDrawnCallback != null) { mService.mWaitingForDrawnCallback.run(); } mService.mWaitingForDrawnCallback = () -> { mAnimationStartDelayed = false; if (mDelayedImeWin != null) { mDelayedImeWin.mWinAnimator.endDelayingAnimationStart(); } // If the adjust status changed since this was posted, only notify // the new states and don't animate. long duration = 0; if (mAdjustedForIme == adjustedForIme && mAdjustedForDivider == adjustedForDivider) { duration = IME_ADJUST_ANIM_DURATION; } else { Slog.w(TAG, "IME adjust changed while waiting for drawn:" + " adjustedForIme=" + adjustedForIme + " adjustedForDivider=" + adjustedForDivider + " mAdjustedForIme=" + mAdjustedForIme + " mAdjustedForDivider=" + mAdjustedForDivider); } notifyAdjustedForImeChanged( mAdjustedForIme || mAdjustedForDivider, duration); }; } else { notifyAdjustedForImeChanged( adjustedForIme || adjustedForDivider, IME_ADJUST_ANIM_DURATION); } } private boolean setMinimizedDockedStack(boolean minimized) { final TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility(); notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable()); return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f); } private boolean isAnimationMaximizing() { return mAnimationTarget == 0f; } public boolean animate(long now) { if (mWindow == null) { return false; } if (mAnimatingForMinimizedDockedStack) { return animateForMinimizedDockedStack(now); } else if (mAnimatingForIme) { return animateForIme(now); } else { if (mDimLayer != null && mDimLayer.isDimming()) { mDimLayer.setLayer(getResizeDimLayer()); } return false; } } private boolean animateForIme(long now) { if (!mAnimationStarted || mAnimationStartDelayed) { mAnimationStarted = true; mAnimationStartTime = now; mAnimationDuration = (long) (IME_ADJUST_ANIM_DURATION * mService.getWindowAnimationScaleLocked()); } float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration); t = (mAnimationTarget == 1f ? IME_ADJUST_ENTRY_INTERPOLATOR : TOUCH_RESPONSE_INTERPOLATOR) .getInterpolation(t); final boolean updated = mDisplayContent.animateForIme(t, mAnimationTarget, mDividerAnimationTarget); if (updated) { mService.mWindowPlacerLocked.performSurfacePlacement(); } if (t >= 1.0f) { mLastAnimationProgress = mAnimationTarget; mLastDividerProgress = mDividerAnimationTarget; mAnimatingForIme = false; return false; } else { return true; } } private boolean animateForMinimizedDockedStack(long now) { final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID); if (!mAnimationStarted) { mAnimationStarted = true; mAnimationStartTime = now; notifyDockedStackMinimizedChanged(mMinimizedDock, true /* animate */, isHomeStackResizable() /* isHomeStackResizable */); } float t = Math.min(1f, (float) (now - mAnimationStartTime) / mAnimationDuration); t = (isAnimationMaximizing() ? TOUCH_RESPONSE_INTERPOLATOR : mMinimizedDockInterpolator) .getInterpolation(t); if (stack != null) { if (stack.setAdjustedForMinimizedDock(getMinimizeAmount(stack, t))) { mService.mWindowPlacerLocked.performSurfacePlacement(); } } if (t >= 1.0f) { mAnimatingForMinimizedDockedStack = false; return false; } else { return true; } } float getInterpolatedAnimationValue(float t) { return t * mAnimationTarget + (1 - t) * mAnimationStart; } float getInterpolatedDividerValue(float t) { return t * mDividerAnimationTarget + (1 - t) * mDividerAnimationStart; } /** * Gets the amount how much to minimize a stack depending on the interpolated fraction t. */ private float getMinimizeAmount(TaskStack stack, float t) { final float naturalAmount = getInterpolatedAnimationValue(t); if (isAnimationMaximizing()) { return adjustMaximizeAmount(stack, t, naturalAmount); } else { return naturalAmount; } } /** * When maximizing the stack during a clip reveal transition, this adjusts the minimize amount * during the transition such that the edge of the clip reveal rect is met earlier in the * transition so we don't create a visible "hole", but only if both the clip reveal and the * docked stack divider start from about the same portion on the screen. */ private float adjustMaximizeAmount(TaskStack stack, float t, float naturalAmount) { if (mMaximizeMeetFraction == 1f) { return naturalAmount; } final int minimizeDistance = stack.getMinimizeDistance(); float startPrime = mService.mAppTransition.getLastClipRevealMaxTranslation() / (float) minimizeDistance; final float amountPrime = t * mAnimationTarget + (1 - t) * startPrime; final float t2 = Math.min(t / mMaximizeMeetFraction, 1); return amountPrime * t2 + naturalAmount * (1 - t2); } /** * Retrieves the animation fraction at which the docked stack has to meet the clip reveal * edge. See {@link #adjustMaximizeAmount}. */ private float getClipRevealMeetFraction(TaskStack stack) { if (!isAnimationMaximizing() || stack == null || !mService.mAppTransition.hadClipRevealAnimation()) { return 1f; } final int minimizeDistance = stack.getMinimizeDistance(); final float fraction = Math.abs(mService.mAppTransition.getLastClipRevealMaxTranslation()) / (float) minimizeDistance; final float t = Math.max(0, Math.min(1, (fraction - CLIP_REVEAL_MEET_FRACTION_MIN) / (CLIP_REVEAL_MEET_FRACTION_MAX - CLIP_REVEAL_MEET_FRACTION_MIN))); return CLIP_REVEAL_MEET_EARLIEST + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST); } @Override public boolean dimFullscreen() { return false; } @Override public DisplayInfo getDisplayInfo() { return mDisplayContent.getDisplayInfo(); } @Override public boolean isAttachedToDisplay() { return mDisplayContent != null; } @Override public void getDimBounds(Rect outBounds) { // This dim layer user doesn't need this. } @Override public String toShortString() { return TAG; } WindowState getWindow() { return mWindow; } void dump(String prefix, PrintWriter pw) { pw.println(prefix + "DockedStackDividerController"); pw.println(prefix + " mLastVisibility=" + mLastVisibility); pw.println(prefix + " mMinimizedDock=" + mMinimizedDock); pw.println(prefix + " mAdjustedForIme=" + mAdjustedForIme); pw.println(prefix + " mAdjustedForDivider=" + mAdjustedForDivider); if (mDimLayer.isDimming()) { pw.println(prefix + " Dim layer is dimming: "); mDimLayer.printTo(prefix + " ", pw); } } }