Loading packages/SystemUI/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -485,6 +485,8 @@ android_library { "motion_tool_lib", "androidx.core_core-animation-testing-nodeps", "androidx.compose.ui_ui", "flag-junit", "platform-test-annotations", ], } Loading packages/SystemUI/aconfig/accessibility.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -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 packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java +76 −24 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -91,17 +92,49 @@ 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) { moveToPositionX(positionX, /* animateMovement = */ false); } 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); } private void moveToPositionY(float positionY) { 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) { // If the list view was out of screen bounds, it would allow users to nest scroll inside Loading Loading @@ -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() { Loading Loading @@ -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); } Loading Loading @@ -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); Loading @@ -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) Loading @@ -257,7 +287,7 @@ class MenuAnimationController { DynamicAnimation::isRunning); if (!areAnimationsRunning) { onSpringAnimationsEnd(new PointF(mMenuView.getTranslationX(), mMenuView.getTranslationY())); mMenuView.getTranslationY()), writeToPosition); } }) .setStartVelocity(velocity); Loading @@ -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; } Loading @@ -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 Loading Loading @@ -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(); } Loading @@ -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(); Loading @@ -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. Loading @@ -384,8 +424,13 @@ class MenuAnimationController { final float percentageY = position.y < 0 || draggableBounds.height() == 0 ? MIN_PERCENT : Math.min(MAX_PERCENT, position.y / draggableBounds.height()); if (Flags.floatingMenuImeDisplacementAnimation() && !writeToPosition) { mMenuView.onEdgeChangedIfNeeded(); } else { mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY)); } } void updateOpacityWith(boolean isFadeEffectEnabled, float newOpacityValue) { mIsFadeEffectEnabled = isFadeEffectEnabled; Loading Loading @@ -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); } } packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +25 −3 Original line number Diff line number Diff line Loading @@ -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 = Loading @@ -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) { Loading Loading @@ -197,8 +197,30 @@ class MenuView extends FrameLayout implements } void onPositionChanged() { final PointF position = mMenuViewAppearance.getMenuPosition(); 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()) { Loading packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +6 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } Loading Loading @@ -362,6 +363,10 @@ class MenuViewLayer extends FrameLayout implements mMenuAnimationController.startTuckedAnimationPreview(); } if (Flags.floatingMenuImeDisplacementAnimation()) { mMenuView.onArrivalAtPosition(); } } private CharSequence getMigrationMessage() { Loading Loading
packages/SystemUI/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -485,6 +485,8 @@ android_library { "motion_tool_lib", "androidx.core_core-animation-testing-nodeps", "androidx.compose.ui_ui", "flag-junit", "platform-test-annotations", ], } Loading
packages/SystemUI/aconfig/accessibility.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -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
packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java +76 −24 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -91,17 +92,49 @@ 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) { moveToPositionX(positionX, /* animateMovement = */ false); } 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); } private void moveToPositionY(float positionY) { 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) { // If the list view was out of screen bounds, it would allow users to nest scroll inside Loading Loading @@ -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() { Loading Loading @@ -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); } Loading Loading @@ -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); Loading @@ -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) Loading @@ -257,7 +287,7 @@ class MenuAnimationController { DynamicAnimation::isRunning); if (!areAnimationsRunning) { onSpringAnimationsEnd(new PointF(mMenuView.getTranslationX(), mMenuView.getTranslationY())); mMenuView.getTranslationY()), writeToPosition); } }) .setStartVelocity(velocity); Loading @@ -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; } Loading @@ -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 Loading Loading @@ -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(); } Loading @@ -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(); Loading @@ -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. Loading @@ -384,8 +424,13 @@ class MenuAnimationController { final float percentageY = position.y < 0 || draggableBounds.height() == 0 ? MIN_PERCENT : Math.min(MAX_PERCENT, position.y / draggableBounds.height()); if (Flags.floatingMenuImeDisplacementAnimation() && !writeToPosition) { mMenuView.onEdgeChangedIfNeeded(); } else { mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY)); } } void updateOpacityWith(boolean isFadeEffectEnabled, float newOpacityValue) { mIsFadeEffectEnabled = isFadeEffectEnabled; Loading Loading @@ -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); } }
packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +25 −3 Original line number Diff line number Diff line Loading @@ -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 = Loading @@ -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) { Loading Loading @@ -197,8 +197,30 @@ class MenuView extends FrameLayout implements } void onPositionChanged() { final PointF position = mMenuViewAppearance.getMenuPosition(); 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()) { Loading
packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +6 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } Loading Loading @@ -362,6 +363,10 @@ class MenuViewLayer extends FrameLayout implements mMenuAnimationController.startTuckedAnimationPreview(); } if (Flags.floatingMenuImeDisplacementAnimation()) { mMenuView.onArrivalAtPosition(); } } private CharSequence getMigrationMessage() { Loading