diff options
| author | 2023-10-25 20:33:18 +0000 | |
|---|---|---|
| committer | 2023-10-25 20:33:18 +0000 | |
| commit | c88b5c6727ebcd6c94a13b82288845547c818f4d (patch) | |
| tree | 4365f323fb0aa17ff447de860627e3689d9dc607 | |
| parent | 3871106cdfd948d610717a7f74fccc46ac1ba1c2 (diff) | |
| parent | 84156fc1b77b9d7a023604382178a4f2d9dc7bec (diff) | |
Merge "Implement smooth FAB animations for displacement due to IME visibility changes" into main
8 files changed, 189 insertions, 35 deletions
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index e6a82e83433b..17cc9f8135f4 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -485,6 +485,8 @@ android_library { "motion_tool_lib", "androidx.core_core-animation-testing-nodeps", "androidx.compose.ui_ui", + "flag-junit", + "platform-test-annotations", ], } diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index bcf1535b94fa..08ecf09b2365 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -8,3 +8,10 @@ flag { description: "Adjusts bounds to allow the floating menu to render on top of navigation bars." bug: "283768342" } + +flag { + name: "floating_menu_ime_displacement_animation" + namespace: "accessibility" + description: "Adds an animation for when the FAB is displaced by an IME becoming visible." + bug: "281150010" +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java index 105de16ce66b..cd8bef1ab6ed 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java @@ -38,6 +38,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import com.android.systemui.Flags; import java.util.HashMap; @@ -91,16 +92,48 @@ class MenuAnimationController { } void moveToPosition(PointF position) { - moveToPositionX(position.x); - moveToPositionY(position.y); + moveToPosition(position, /* animateMovement = */ false); + } + + /* Moves position without updating underlying percentage position. Can be animated. */ + void moveToPosition(PointF position, boolean animateMovement) { + if (Flags.floatingMenuImeDisplacementAnimation()) { + moveToPositionX(position.x, animateMovement); + moveToPositionY(position.y, animateMovement); + } else { + moveToPositionX(position.x, /* animateMovement = */ false); + moveToPositionY(position.y, /* animateMovement = */ false); + } } void moveToPositionX(float positionX) { - DynamicAnimation.TRANSLATION_X.setValue(mMenuView, positionX); + moveToPositionX(positionX, /* animateMovement = */ false); } - private void moveToPositionY(float positionY) { - DynamicAnimation.TRANSLATION_Y.setValue(mMenuView, positionY); + void moveToPositionX(float positionX, boolean animateMovement) { + if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) { + springMenuWith(DynamicAnimation.TRANSLATION_X, + createSpringForce(), + /* velocity = */ 0, + positionX, /* writeToPosition = */ false); + } else { + DynamicAnimation.TRANSLATION_X.setValue(mMenuView, positionX); + } + } + + void moveToPositionY(float positionY) { + moveToPositionY(positionY, /* animateMovement = */ false); + } + + void moveToPositionY(float positionY, boolean animateMovement) { + if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) { + springMenuWith(DynamicAnimation.TRANSLATION_Y, + createSpringForce(), + /* velocity = */ 0, + positionY, /* writeToPosition = */ false); + } else { + DynamicAnimation.TRANSLATION_Y.setValue(mMenuView, positionY); + } } void moveToPositionYIfNeeded(float positionY) { @@ -151,7 +184,7 @@ class MenuAnimationController { void moveAndPersistPosition(PointF position) { moveToPosition(position); mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y); - constrainPositionAndUpdate(position); + constrainPositionAndUpdate(position, /* writeToPosition = */ true); } void removeMenu() { @@ -180,17 +213,13 @@ class MenuAnimationController { flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X, startXVelocity, FLING_FRICTION_SCALAR, - new SpringForce() - .setStiffness(SPRING_STIFFNESS) - .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO), + createSpringForce(), finalPositionX); flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_Y, velocityY, FLING_FRICTION_SCALAR, - new SpringForce() - .setStiffness(SPRING_STIFFNESS) - .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO), + createSpringForce(), /* finalPosition= */ null); } @@ -226,7 +255,8 @@ class MenuAnimationController { final float endPosition = finalPosition != null ? finalPosition : Math.max(min, Math.min(max, endValue)); - springMenuWith(property, spring, endVelocity, endPosition); + springMenuWith(property, spring, endVelocity, endPosition, + /* writeToPosition = */ true); }); cancelAnimation(property); @@ -242,7 +272,7 @@ class MenuAnimationController { @VisibleForTesting void springMenuWith(DynamicAnimation.ViewProperty property, SpringForce spring, - float velocity, float finalPosition) { + float velocity, float finalPosition, boolean writeToPosition) { final MenuPositionProperty menuPositionProperty = new MenuPositionProperty(property); final SpringAnimation springAnimation = new SpringAnimation(mMenuView, menuPositionProperty) @@ -257,7 +287,7 @@ class MenuAnimationController { DynamicAnimation::isRunning); if (!areAnimationsRunning) { onSpringAnimationsEnd(new PointF(mMenuView.getTranslationX(), - mMenuView.getTranslationY())); + mMenuView.getTranslationY()), writeToPosition); } }) .setStartVelocity(velocity); @@ -281,7 +311,8 @@ class MenuAnimationController { if (currentXTranslation < draggableBounds.left || currentXTranslation > draggableBounds.right) { constrainPositionAndUpdate( - new PointF(mMenuView.getTranslationX(), mMenuView.getTranslationY())); + new PointF(mMenuView.getTranslationX(), mMenuView.getTranslationY()), + /* writeToPosition = */ true); moveToEdgeAndHide(); return true; } @@ -298,15 +329,19 @@ class MenuAnimationController { return mMenuView.isMoveToTucked(); } - void moveToEdgeAndHide() { - mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true); - + PointF getTuckedMenuPosition() { final PointF position = mMenuView.getMenuPosition(); final float menuHalfWidth = mMenuView.getMenuWidth() / 2.0f; final float endX = isOnLeftSide() ? position.x - menuHalfWidth : position.x + menuHalfWidth; - moveToPosition(new PointF(endX, position.y)); + return new PointF(endX, position.y); + } + + void moveToEdgeAndHide() { + mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true); + final PointF position = mMenuView.getMenuPosition(); + moveToPosition(getTuckedMenuPosition()); // Keep the touch region let users could click extra space to pop up the menu view // from the screen edge @@ -335,6 +370,11 @@ class MenuAnimationController { mPositionAnimations.get(property).cancel(); } + @VisibleForTesting + DynamicAnimation getAnimation(DynamicAnimation.ViewProperty property) { + return mPositionAnimations.getOrDefault(property, null); + } + void onDraggingStart() { mMenuView.onDraggingStart(); } @@ -361,9 +401,9 @@ class MenuAnimationController { .start(); } - private void onSpringAnimationsEnd(PointF position) { + private void onSpringAnimationsEnd(PointF position, boolean writeToPosition) { mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y); - constrainPositionAndUpdate(position); + constrainPositionAndUpdate(position, writeToPosition); fadeOutIfEnabled(); @@ -372,7 +412,7 @@ class MenuAnimationController { } } - private void constrainPositionAndUpdate(PointF position) { + private void constrainPositionAndUpdate(PointF position, boolean writeToPosition) { final Rect draggableBounds = mMenuView.getMenuDraggableBoundsExcludeIme(); // Have the space gap margin between the top bound and the menu view, so actually the // position y range needs to cut the margin. @@ -384,7 +424,12 @@ class MenuAnimationController { final float percentageY = position.y < 0 || draggableBounds.height() == 0 ? MIN_PERCENT : Math.min(MAX_PERCENT, position.y / draggableBounds.height()); - mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY)); + + if (Flags.floatingMenuImeDisplacementAnimation() && !writeToPosition) { + mMenuView.onEdgeChangedIfNeeded(); + } else { + mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY)); + } } void updateOpacityWith(boolean isFadeEffectEnabled, float newOpacityValue) { @@ -463,4 +508,11 @@ class MenuAnimationController { mProperty.setValue(menuView, value); } } + + @VisibleForTesting + static SpringForce createSpringForce() { + return new SpringForce() + .setStiffness(SPRING_STIFFNESS) + .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO); + } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java index e1612b071458..ea5a56c6a0f5 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -54,7 +54,6 @@ class MenuView extends FrameLayout implements private final List<AccessibilityTarget> mTargetFeatures = new ArrayList<>(); private final AccessibilityTargetAdapter mAdapter; private final MenuViewModel mMenuViewModel; - private final MenuAnimationController mMenuAnimationController; private final Rect mBoundsInParent = new Rect(); private final RecyclerView mTargetFeaturesView; private final ViewTreeObserver.OnDrawListener mSystemGestureExcludeUpdater = @@ -70,6 +69,7 @@ class MenuView extends FrameLayout implements private boolean mIsMoveToTucked; + private final MenuAnimationController mMenuAnimationController; private OnTargetFeaturesChangeListener mFeaturesChangeListener; MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) { @@ -197,8 +197,30 @@ class MenuView extends FrameLayout implements } void onPositionChanged() { - final PointF position = mMenuViewAppearance.getMenuPosition(); - mMenuAnimationController.moveToPosition(position); + onPositionChanged(/* animateMovement = */ false); + } + + void onPositionChanged(boolean animateMovement) { + final PointF position; + if (isMoveToTucked()) { + position = mMenuAnimationController.getTuckedMenuPosition(); + } else { + position = getMenuPosition(); + } + + // We can skip animating if FAB is not visible + if (Flags.floatingMenuImeDisplacementAnimation() + && animateMovement && getVisibility() == VISIBLE) { + mMenuAnimationController.moveToPosition(position, /* animateMovement = */ true); + // onArrivalAtPosition() is called at the end of the animation. + } else { + mMenuAnimationController.moveToPosition(position); + onArrivalAtPosition(); // no animation, so we call this immediately. + } + } + + void onArrivalAtPosition() { + final PointF position = getMenuPosition(); onBoundsInParentChanged((int) position.x, (int) position.y); if (isMoveToTucked()) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java index b6e8997c31e7..fbca02290236 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java @@ -59,6 +59,7 @@ import androidx.lifecycle.Observer; import com.android.internal.accessibility.dialog.AccessibilityTarget; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import com.android.systemui.Flags; import com.android.systemui.res.R; import com.android.systemui.util.settings.SecureSettings; import com.android.wm.shell.bubbles.DismissViewUtils; @@ -331,7 +332,7 @@ class MenuViewLayer extends FrameLayout implements mMenuViewAppearance.onImeVisibilityChanged(windowInsets.isVisible(ime()), imeTop); mMenuView.onEdgeChanged(); - mMenuView.onPositionChanged(); + mMenuView.onPositionChanged(/* animateMovement = */ true); mImeInsetsRect.set(imeInsetsRect); } @@ -362,6 +363,10 @@ class MenuViewLayer extends FrameLayout implements mMenuAnimationController.startTuckedAnimationPreview(); } + + if (Flags.floatingMenuImeDisplacementAnimation()) { + mMenuView.onArrivalAtPosition(); + } } private CharSequence getMigrationMessage() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java index ec6ec63d213d..e8329409ff10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java @@ -234,10 +234,12 @@ public class MenuAnimationControllerTest extends SysuiTestCase { mMenuAnimationController.springMenuWith(DynamicAnimation.TRANSLATION_X, new SpringForce() .setStiffness(stiffness) - .setDampingRatio(dampingRatio), velocity, finalPosition); + .setDampingRatio(dampingRatio), velocity, finalPosition, + /* writeToPosition = */ true); mMenuAnimationController.springMenuWith(DynamicAnimation.TRANSLATION_Y, new SpringForce() .setStiffness(stiffness) - .setDampingRatio(dampingRatio), velocity, finalPosition); + .setDampingRatio(dampingRatio), velocity, finalPosition, + /* writeToPosition = */ true); } private void skipAnimationToEnd(DynamicAnimation animation) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index aed795a440b4..76094c1cf185 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -42,6 +42,10 @@ import android.graphics.PointF; import android.graphics.Rect; import android.os.Build; import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -52,8 +56,10 @@ import android.view.WindowMetrics; import android.view.accessibility.AccessibilityManager; import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringAnimation; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.util.settings.SecureSettings; @@ -98,6 +104,10 @@ public class MenuViewLayerTest extends SysuiTestCase { @Rule public MockitoRule mockito = MockitoJUnit.rule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Mock private IAccessibilityFloatingMenu mFloatingMenu; @@ -116,7 +126,8 @@ public class MenuViewLayerTest extends SysuiTestCase { final Rect mDisplayBounds = new Rect(); mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH, DISPLAY_WINDOW_HEIGHT); - mWindowMetrics = spy(new WindowMetrics(mDisplayBounds, fakeDisplayInsets())); + mWindowMetrics = spy( + new WindowMetrics(mDisplayBounds, fakeDisplayInsets(), /* density = */ 0.0f)); doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics(); mMenuViewLayer = new MenuViewLayer(mContext, mStubWindowManager, mStubAccessibilityManager, @@ -221,27 +232,62 @@ public class MenuViewLayerTest extends SysuiTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) + public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme_old() { + mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100)); + final PointF beforePosition = mMenuView.getMenuPosition(); + + dispatchShowingImeInsets(); + + final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight(); + assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x); + assertThat(menuBottom).isLessThan(beforePosition.y); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() { mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100)); final PointF beforePosition = mMenuView.getMenuPosition(); dispatchShowingImeInsets(); + assertThat(isPositionAnimationRunning()).isTrue(); + skipPositionAnimations(); final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight(); + assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x); assertThat(menuBottom).isLessThan(beforePosition.y); } @Test + @RequiresFlagsDisabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) + public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition_old() { + mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200)); + final PointF beforePosition = mMenuView.getMenuPosition(); + + dispatchHidingImeInsets(); + + assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x); + assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() { - final float menuTop = IME_TOP + 200; - mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop)); + mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200)); + final PointF beforePosition = mMenuView.getMenuPosition(); + dispatchShowingImeInsets(); + assertThat(isPositionAnimationRunning()).isTrue(); + skipPositionAnimations(); dispatchHidingImeInsets(); + assertThat(isPositionAnimationRunning()).isTrue(); + skipPositionAnimations(); - assertThat(mMenuView.getTranslationX()).isEqualTo(0); - assertThat(mMenuView.getTranslationY()).isEqualTo(menuTop); + assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x); + assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y); } private void setupEnabledAccessibilityServiceList() { @@ -294,4 +340,21 @@ public class MenuViewLayerTest extends SysuiTestCase { Insets.of(/* left= */ 0, /* top= */ 0, /* right= */ 0, bottom)) .build(); } + + private boolean isPositionAnimationRunning() { + return !mMenuAnimationController.mPositionAnimations.values().stream().filter( + (animation) -> animation.isRunning()).findAny().isEmpty(); + } + + private void skipPositionAnimations() { + mMenuAnimationController.mPositionAnimations.values().stream().forEach( + (animation) -> { + final SpringAnimation springAnimation = ((SpringAnimation) animation); + // The doAnimationFrame function is used for skipping animation to the end. + springAnimation.doAnimationFrame(500); + springAnimation.skipToEnd(); + springAnimation.doAnimationFrame(500); + }); + + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java index 297554956ed8..c7bb0f5d3906 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/FlagUtils.java @@ -31,5 +31,6 @@ public class FlagUtils { */ public static void setFlagDefaults(SetFlagsRule setFlagsRule) { setFlagDefault(setFlagsRule, Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG); + setFlagDefault(setFlagsRule, Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION); } } |