From 95729201f90b6a3199fd2072eef588fef3d86b65 Mon Sep 17 00:00:00 2001 From: Evan Rosky Date: Fri, 21 Feb 2020 10:16:08 -0800 Subject: [PATCH] Fix some split-screen IME glitches First of all, this fixes some ime reporting issues. Specifically, the ime state could be updated without a queued traversal which would cause some messages to get lost. Second, this has the Divider switch to "async" animation when stack adjustment is no longer aligned with IME animation. This happens when switching ime focus from one split to the other. Additionally, I had to change the ime animations to remember where they were before "switching directions" because it turns out the insets logic varies when focus changes: sometimes you get only a show() other times you get a hide() followed immediately by a show() Bug: 133381284 Bug: 149952263 Bug: 150702357 Test: manually open split-screen with editors in each split and try switching between them. Change-Id: I8c92ce4e74651f08ef84d4f1c4b2ddb9625123f1 --- .../systemui/stackdivider/Divider.java | 216 ++++++++++++++---- .../systemui/stackdivider/DividerView.java | 3 + .../stackdivider/SplitDisplayLayout.java | 21 +- .../systemui/wm/DisplayImeController.java | 80 +++++-- .../server/wm/ImeInsetsSourceProvider.java | 7 +- 5 files changed, 240 insertions(+), 87 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java index 27b799bc02a36..333fa3ca0a2d0 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java @@ -19,6 +19,9 @@ package com.android.systemui.stackdivider; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.Display.DEFAULT_DISPLAY; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.app.ActivityTaskManager; import android.content.Context; import android.content.res.Configuration; @@ -35,6 +38,8 @@ import android.view.SurfaceSession; import android.view.View; import android.view.WindowContainerTransaction; +import androidx.annotation.Nullable; + import com.android.internal.policy.DividerSnapAlgorithm; import com.android.systemui.R; import com.android.systemui.SystemUI; @@ -69,6 +74,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, static final boolean DEBUG = true; static final int DEFAULT_APP_TRANSITION_DURATION = 336; + static final float ADJUSTED_NONFOCUS_DIM = 0.3f; private final Optional> mRecentsOptionalLazy; @@ -121,42 +127,92 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, } }; - private IWindowContainer mLastImeTarget = null; - private boolean mShouldAdjustForIme = false; - private DisplayImeController.ImePositionProcessor mImePositionProcessor = new DisplayImeController.ImePositionProcessor() { - private int mStartTop = 0; - private int mFinalTop = 0; + /** + * These are the y positions of the top of the IME surface when it is hidden and + * when it is shown respectively. These are NOT necessarily the top of the visible + * IME itself. + */ + private int mHiddenTop = 0; + private int mShownTop = 0; + + // The following are target states (what we are curretly animating towards). + /** + * {@code true} if, at the end of the animation, the split task positions should be + * adjusted by height of the IME. This happens when the secondary split is the IME + * target. + */ + private boolean mTargetAdjusted = false; + /** + * {@code true} if, at the end of the animation, the IME should be shown/visible + * regardless of what has focus. + */ + private boolean mTargetShown = false; + + // The following are the current (most recent) states set during animation + /** + * {@code true} if the secondary split has IME focus. + */ + private boolean mSecondaryHasFocus = false; + /** The dimming currently applied to the primary/secondary splits. */ + private float mLastPrimaryDim = 0.f; + private float mLastSecondaryDim = 0.f; + /** The most recent y position of the top of the IME surface */ + private int mLastAdjustTop = -1; + + // The following are states reached last time an animation fully completed. + /** {@code true} if the IME was shown/visible by the last-completed animation. */ + private boolean mImeWasShown = false; + /** + * {@code true} if the split positions were adjusted by the last-completed + * animation. + */ + private boolean mAdjusted = false; + + /** + * When some aspect of split-screen needs to animate independent from the IME, + * this will be non-null and control split animation. + */ + @Nullable + private ValueAnimator mAnimation = null; + + private boolean getSecondaryHasFocus(int displayId) { + try { + IWindowContainer imeSplit = ActivityTaskManager.getTaskOrganizerController() + .getImeTarget(displayId); + return imeSplit != null + && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder()); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to get IME target", e); + } + return false; + } + @Override - public void onImeStartPositioning(int displayId, int imeTop, int finalImeTop, - boolean showing, SurfaceControl.Transaction t) { - mStartTop = imeTop; - mFinalTop = finalImeTop; - if (showing) { - try { - mLastImeTarget = ActivityTaskManager.getTaskOrganizerController() - .getImeTarget(displayId); - mShouldAdjustForIme = mLastImeTarget != null - && !mSplitLayout.mDisplayLayout.isLandscape() - && (mLastImeTarget.asBinder() - == mSplits.mSecondary.token.asBinder()); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to get IME target", e); - } + public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop, + boolean imeShouldShow, SurfaceControl.Transaction t) { + mSecondaryHasFocus = getSecondaryHasFocus(displayId); + mTargetAdjusted = imeShouldShow && mSecondaryHasFocus + && !mSplitLayout.mDisplayLayout.isLandscape(); + mHiddenTop = hiddenTop; + mShownTop = shownTop; + mTargetShown = imeShouldShow; + if (mLastAdjustTop < 0) { + mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop; } - if (!mShouldAdjustForIme) { - setAdjustedForIme(false); - return; + if (mAnimation != null || (mImeWasShown && imeShouldShow + && mTargetAdjusted != mAdjusted)) { + // We need to animate adjustment independently of the IME position, so + // start our own animation to drive adjustment. This happens when a + // different split's editor has gained focus while the IME is still visible. + startAsyncAnimation(); } - mView.setAdjustedForIme(showing, showing - ? DisplayImeController.ANIMATION_DURATION_SHOW_MS - : DisplayImeController.ANIMATION_DURATION_HIDE_MS); // Reposition the server's secondary split position so that it evaluates // insets properly. WindowContainerTransaction wct = new WindowContainerTransaction(); - if (showing) { - mSplitLayout.updateAdjustedBounds(finalImeTop, imeTop, finalImeTop); + if (mTargetAdjusted) { + mSplitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop); wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mAdjustedSecondary); } else { wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mSecondary); @@ -166,34 +222,106 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, .applyContainerTransaction(wct, null /* organizer */); } catch (RemoteException e) { } - setAdjustedForIme(showing); + + // Update all the adjusted-for-ime states + mView.setAdjustedForIme(mTargetShown, mTargetShown + ? DisplayImeController.ANIMATION_DURATION_SHOW_MS + : DisplayImeController.ANIMATION_DURATION_HIDE_MS); + setAdjustedForIme(mTargetShown); } @Override public void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) { - if (!mShouldAdjustForIme) { + if (mAnimation != null) { + // Not synchronized with IME anymore, so return. return; } - mSplitLayout.updateAdjustedBounds(imeTop, mStartTop, mFinalTop); - mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary, - mSplitLayout.mAdjustedSecondary); - final boolean showing = mFinalTop < mStartTop; - final float progress = ((float) (imeTop - mStartTop)) / (mFinalTop - mStartTop); - final float fraction = showing ? progress : 1.f - progress; - mView.setResizeDimLayer(t, true /* primary */, fraction * 0.3f); + final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop); + final float progress = mTargetShown ? fraction : 1.f - fraction; + onProgress(progress, t); } @Override - public void onImeEndPositioning(int displayId, int imeTop, - boolean showing, SurfaceControl.Transaction t) { - if (!mShouldAdjustForIme) { + public void onImeEndPositioning(int displayId, boolean cancelled, + SurfaceControl.Transaction t) { + if (mAnimation != null) { + // Not synchronized with IME anymore, so return. return; } - mSplitLayout.updateAdjustedBounds(imeTop, mStartTop, mFinalTop); - mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary, - mSplitLayout.mAdjustedSecondary); - mView.setResizeDimLayer(t, true /* primary */, showing ? 0.3f : 0.f); + onEnd(cancelled, t); + } + + private void onProgress(float progress, SurfaceControl.Transaction t) { + if (mTargetAdjusted != mAdjusted) { + final float fraction = mTargetAdjusted ? progress : 1.f - progress; + mLastAdjustTop = + (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop); + mSplitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop); + mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary, + mSplitLayout.mAdjustedSecondary); + } + final float invProg = 1.f - progress; + final float targetPrimaryDim = (mSecondaryHasFocus && mTargetShown) + ? ADJUSTED_NONFOCUS_DIM : 0.f; + final float targetSecondaryDim = (!mSecondaryHasFocus && mTargetShown) + ? ADJUSTED_NONFOCUS_DIM : 0.f; + mView.setResizeDimLayer(t, true /* primary */, + mLastPrimaryDim * invProg + progress * targetPrimaryDim); + mView.setResizeDimLayer(t, false /* primary */, + mLastSecondaryDim * invProg + progress * targetSecondaryDim); + } + + private void onEnd(boolean cancelled, SurfaceControl.Transaction t) { + if (!cancelled) { + onProgress(1.f, t); + mAdjusted = mTargetAdjusted; + mImeWasShown = mTargetShown; + mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop; + mLastPrimaryDim = + (mSecondaryHasFocus && mTargetShown) ? ADJUSTED_NONFOCUS_DIM : 0.f; + mLastSecondaryDim = + (!mSecondaryHasFocus && mTargetShown) ? ADJUSTED_NONFOCUS_DIM : 0.f; + } + } + + private void startAsyncAnimation() { + if (mAnimation != null) { + mAnimation.cancel(); + } + mAnimation = ValueAnimator.ofFloat(0.f, 1.f); + mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS); + if (mTargetAdjusted != mAdjusted) { + final float fraction = + ((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop); + final float progress = mTargetAdjusted ? fraction : 1.f - fraction; + mAnimation.setCurrentFraction(progress); + } + + mAnimation.addUpdateListener(animation -> { + SurfaceControl.Transaction t = mTransactionPool.acquire(); + float value = (float) animation.getAnimatedValue(); + onProgress(value, t); + t.apply(); + mTransactionPool.release(t); + }); + mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR); + mAnimation.addListener(new AnimatorListenerAdapter() { + private boolean mCancel = false; + @Override + public void onAnimationCancel(Animator animation) { + mCancel = true; + } + @Override + public void onAnimationEnd(Animator animation) { + SurfaceControl.Transaction t = mTransactionPool.acquire(); + onEnd(mCancel, t); + t.apply(); + mTransactionPool.release(t); + mAnimation = null; + } + }); + mAnimation.start(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index be9fcbf19f120..477cbb7c7ad07 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -935,6 +935,9 @@ public class DividerView extends FrameLayout implements OnTouchListener, } public void setAdjustedForIme(boolean adjustedForIme, long animDuration) { + if (mAdjustedForIme == adjustedForIme) { + return; + } updateDockSide(); mHandle.animate() .setInterpolator(IME_ADJUST_INTERPOLATOR) diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java index b19f560f2f50a..271faed54bcab 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java @@ -171,22 +171,13 @@ public class SplitDisplayLayout { /** * Updates the adjustment depending on it's current state. */ - void updateAdjustedBounds(int currImeTop, int startTop, int finalTop) { - updateAdjustedBounds(mDisplayLayout, currImeTop, startTop, finalTop, mDividerSize, + void updateAdjustedBounds(int currImeTop, int hiddenTop, int shownTop) { + adjustForIME(mDisplayLayout, currImeTop, hiddenTop, shownTop, mDividerSize, mDividerSizeInactive, mPrimary, mSecondary); } - /** - * Updates the adjustment depending on it's current state. - */ - private void updateAdjustedBounds(DisplayLayout dl, int currImeTop, int startTop, int finalTop, - int dividerWidth, int dividerWidthInactive, Rect primaryBounds, Rect secondaryBounds) { - adjustForIME(dl, currImeTop, startTop, finalTop, dividerWidth, dividerWidthInactive, - primaryBounds, secondaryBounds); - } - /** Assumes top/bottom split. Splits are not adjusted for left/right splits. */ - private void adjustForIME(DisplayLayout dl, int currImeTop, int startTop, int finalTop, + private void adjustForIME(DisplayLayout dl, int currImeTop, int hiddenTop, int shownTop, int dividerWidth, int dividerWidthInactive, Rect primaryBounds, Rect secondaryBounds) { if (mAdjustedPrimary == null) { mAdjustedPrimary = new Rect(); @@ -196,11 +187,9 @@ public class SplitDisplayLayout { final Rect displayStableRect = new Rect(); dl.getStableBounds(displayStableRect); - final boolean showing = finalTop < startTop; - final float progress = ((float) (currImeTop - startTop)) / (finalTop - startTop); - final float dividerSquish = showing ? progress : 1.f - progress; + final float shownFraction = ((float) (currImeTop - hiddenTop)) / (shownTop - hiddenTop); final int currDividerWidth = - (int) (dividerWidthInactive * dividerSquish + dividerWidth * (1.f - dividerSquish)); + (int) (dividerWidthInactive * shownFraction + dividerWidth * (1.f - shownFraction)); final int minTopStackBottom = displayStableRect.top + (int) ((mPrimary.bottom - displayStableRect.top) * ADJUSTED_STACK_FRACTION_MIN); diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java index 1b62cbfabe397..2f1b160ffd4ba 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java @@ -51,7 +51,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged public static final int ANIMATION_DURATION_SHOW_MS = 275; public static final int ANIMATION_DURATION_HIDE_MS = 340; - static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); + public static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); private static final int DIRECTION_NONE = 0; private static final int DIRECTION_SHOW = 1; private static final int DIRECTION_HIDE = 2; @@ -127,20 +127,20 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } } - private void dispatchStartPositioning(int displayId, int imeTop, int finalImeTop, + private void dispatchStartPositioning(int displayId, int hiddenTop, int shownTop, boolean show, SurfaceControl.Transaction t) { synchronized (mPositionProcessors) { for (ImePositionProcessor pp : mPositionProcessors) { - pp.onImeStartPositioning(displayId, imeTop, finalImeTop, show, t); + pp.onImeStartPositioning(displayId, hiddenTop, shownTop, show, t); } } } - private void dispatchEndPositioning(int displayId, int imeTop, boolean show, + private void dispatchEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t) { synchronized (mPositionProcessors) { for (ImePositionProcessor pp : mPositionProcessors) { - pp.onImeEndPositioning(displayId, imeTop, show, t); + pp.onImeEndPositioning(displayId, cancel, t); } } } @@ -173,6 +173,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged int mAnimationDirection = DIRECTION_NONE; ValueAnimator mAnimation = null; int mRotation = Surface.ROTATION_0; + boolean mImeShowing = false; PerDisplay(int displayId, int initialRotation) { mDisplayId = displayId; @@ -239,23 +240,39 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged if (imeSource == null || mImeSourceControl == null) { return; } - if ((mAnimationDirection == DIRECTION_SHOW && show) - || (mAnimationDirection == DIRECTION_HIDE && !show)) { - return; - } - if (mAnimationDirection != DIRECTION_NONE) { - mAnimation.end(); - mAnimationDirection = DIRECTION_NONE; - } - mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; mHandler.post(() -> { + if ((mAnimationDirection == DIRECTION_SHOW && show) + || (mAnimationDirection == DIRECTION_HIDE && !show)) { + return; + } + boolean seek = false; + float seekValue = 0; + if (mAnimation != null) { + if (mAnimation.isRunning()) { + seekValue = (float) mAnimation.getAnimatedValue(); + seek = true; + } + mAnimation.cancel(); + } + mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; final float defaultY = mImeSourceControl.getSurfacePosition().y; final float x = mImeSourceControl.getSurfacePosition().x; - final float startY = show ? defaultY + imeSource.getFrame().height() : defaultY; - final float endY = show ? defaultY : defaultY + imeSource.getFrame().height(); + final float hiddenY = defaultY + imeSource.getFrame().height(); + final float shownY = defaultY; + final float startY = show ? hiddenY : shownY; + final float endY = show ? shownY : hiddenY; + if (mImeShowing && show) { + // IME is already showing, so set seek to end + seekValue = shownY; + seek = true; + } + mImeShowing = show; mAnimation = ValueAnimator.ofFloat(startY, endY); mAnimation.setDuration( show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS); + if (seek) { + mAnimation.setCurrentFraction((seekValue - startY) / (endY - startY)); + } mAnimation.addUpdateListener(animation -> { SurfaceControl.Transaction t = mTransactionPool.acquire(); @@ -267,12 +284,13 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged }); mAnimation.setInterpolator(INTERPOLATOR); mAnimation.addListener(new AnimatorListenerAdapter() { + private boolean mCancelled = false; @Override public void onAnimationStart(Animator animation) { SurfaceControl.Transaction t = mTransactionPool.acquire(); t.setPosition(mImeSourceControl.getLeash(), x, startY); - dispatchStartPositioning(mDisplayId, imeTop(imeSource, startY), - imeTop(imeSource, endY), mAnimationDirection == DIRECTION_SHOW, + dispatchStartPositioning(mDisplayId, imeTop(imeSource, hiddenY), + imeTop(imeSource, shownY), mAnimationDirection == DIRECTION_SHOW, t); if (mAnimationDirection == DIRECTION_SHOW) { t.show(mImeSourceControl.getLeash()); @@ -281,12 +299,17 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged mTransactionPool.release(t); } @Override + public void onAnimationCancel(Animator animation) { + mCancelled = true; + } + @Override public void onAnimationEnd(Animator animation) { SurfaceControl.Transaction t = mTransactionPool.acquire(); - t.setPosition(mImeSourceControl.getLeash(), x, endY); - dispatchEndPositioning(mDisplayId, imeTop(imeSource, endY), - mAnimationDirection == DIRECTION_SHOW, t); - if (mAnimationDirection == DIRECTION_HIDE) { + if (!mCancelled) { + t.setPosition(mImeSourceControl.getLeash(), x, endY); + } + dispatchEndPositioning(mDisplayId, mCancelled, t); + if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) { t.hide(mImeSourceControl.getLeash()); } t.apply(); @@ -317,21 +340,30 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged public interface ImePositionProcessor { /** * Called when the IME position is starting to animate. + * + * @param hiddenTop The y position of the top of the IME surface when it is hidden. + * @param shownTop The y position of the top of the IME surface when it is shown. + * @param showing {@code true} when we are animating from hidden to shown, {@code false} + * when animating from shown to hidden. */ - default void onImeStartPositioning(int displayId, int imeTop, int finalImeTop, + default void onImeStartPositioning(int displayId, int hiddenTop, int shownTop, boolean showing, SurfaceControl.Transaction t) {} /** * Called when the ime position changed. This is expected to be a synchronous call on the * animation thread. Operations can be added to the transaction to be applied in sync. + * + * @param imeTop The current y position of the top of the IME surface. */ default void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) {} /** * Called when the IME position is done animating. + * + * @param cancel {@code true} if this was cancelled. This implies another start is coming. */ - default void onImeEndPositioning(int displayId, int imeTop, boolean showing, + default void onImeEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t) {} } } diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 725596819b79a..7491376cd1527 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -64,13 +64,14 @@ class ImeInsetsSourceProvider extends InsetsSourceProvider { ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner"); // Target should still be the same. if (isImeTargetFromDisplayContentAndImeSame()) { + final InsetsControlTarget target = mDisplayContent.mInputMethodControlTarget; ProtoLog.d(WM_DEBUG_IME, "call showInsets(ime) on %s", - mDisplayContent.mInputMethodControlTarget.getWindow().getName()); - mDisplayContent.mInputMethodControlTarget.showInsets( - WindowInsets.Type.ime(), true /* fromIme */); + target.getWindow() != null ? target.getWindow().getName() : ""); + target.showInsets(WindowInsets.Type.ime(), true /* fromIme */); } abortShowImePostLayout(); }; + mDisplayContent.mWmService.requestTraversal(); } void checkShowImePostLayout() {